Ciclo de vida de entrega
Todo mensaje saliente que envías a través de Orbit lleva un campostatus que avanza a medida que el mensaje va desde tu llamada a la API hacia el dispositivo del destinatario — o hacia un resultado de fallo. Esta página explica ese autómata de estados a nivel conceptual: qué significan los estados, qué impulsa cada transición y dónde están las particularidades por operador. Léela antes de suscribirte a tu primer webhook o de ramificar tu integración en resultados de mensajes.
La semántica por estado, la tabla completa de transiciones y el mapa de eventos de webhook están en la referencia del ciclo de vida de estados de mensajes; el esquema de respuesta para leer el estado actual de un mensaje está en la referencia de la API de Messaging. Esta página une ambas a un nivel superior.
El camino feliz
Un mensaje que tiene éxito de punta a punta pasa por:pending → queued → sending → sent → delivered → read
Cada transición la avanza un actor distinto — ninguna parte individual ve todo el recorrido:
Dos actores están al margen de este camino y pueden sacar un registro de él:
- El planificador de no-DLR. Cuando un operador nunca devuelve un acuse de entrega, un planificador promueve
sentasubmitted_no_receipttras una ventana de gracia por canal — véase Confirmado por operador vs. centinela intermedio de cable. - Tú (el operador). Cancelar un mensaje programado aún no enviado lo mueve a
cancelled, un estado terminal que ninguna llamada al proveedor y ningún DLR tocan jamás. Los envíos de sandbox se resuelven entest_sent, un estado terminal alcanzado antes de cualquier despacho al proveedor. Borrar un registro mueve cualquier estado terminal adeleted, tras lo cual nada puede tocarlo.
scheduled hasta su hora de disparo y luego se une a la cola. Solo puede salir de scheduled de tres maneras: promovido a queued en la hora de disparo, cancelled por ti, o expired si su ventana de validez vence antes del envío.
Confirmado por operador vs. centinela intermedio de cable
La distinción que más importa para informes y conciliación es si un estado es un resultado confirmado por operador o un centinela intermedio de cable:deliveredyreadestán confirmados por operador. Llegó un DLR real; el propio operador afirmó el resultado.submitted_no_receiptes intermedio de cable. Significa «el envío fue aceptado y no volvió ningún acuse dentro de la ventana de gracia». Se etiqueta como intermedio (state_class: "intermediate",is_terminal: false) en todo webhook, porque un DLR dedelivered,reado fallo genuino puede llegar después y sobrescribirlo.
submitted_no_receipt como resultado desconocido, no como entrega. Que «desconocido» se acerque a positivo o sea realmente ambiguo depende del canal — véase Particularidades por canal.
expired es la contrapartida en el otro lado: llegó un DLR, pero tan tarde que la ventana de acuse ya se había cerrado. El resultado es indeterminable y el registro queda cerrado; expired se distribuye a los suscriptores como un evento message.failed.
Particularidades por canal
- Canales DM de Meta (Instagram, Messenger). La API de envío de Meta nunca emite acuses de entrega. El envío sale de Orbit como
sent, se marcano_dlr_channelen los metadatos del mensaje y pasa asubmitted_no_receipta los 5 minutos. Para un destinatario con opt-in, Meta garantiza la entrega en la aceptación — así que en estos canalessubmitted_no_receiptse comporta como una señal funcional de entrega, y los metadatos del mensaje llevanno_dlr_channel: truepara que puedas distinguir ese caso. - Canales con respaldo SMPP (SMS, MMS, voz, fax, RCS). La ventana de gracia es de 30 minutos. Aquí
submitted_no_receiptes realmente ambiguo: el dispositivo puede haber recibido el mensaje sin que se informe acuse, el operador puede no enviar nunca acuses en esa ruta, o el acuse puede haberse perdido en tránsito. Vigila esta tasa por separado de tu tasa de entregados — una proporción creciente desubmitted_no_receipten un destino apunta a una ruta no cooperativa o a una ruta de acuses rota, y merece investigación en cualquier caso. - Correo electrónico. Añade un resultado de fallo que otros canales no tienen:
bounced, cuando el servidor de correo receptor rechaza el mensaje. Bounced cuenta contra tu tasa de fallos terminales igual quefailed, pero es un estado distinto para que puedas separar rechazos del lado del destinatario de los del lado del proveedor. - Cancelación del operador.
cancelledsolo es alcanzable por ti — mediantePOST /messages/:id/cancelen un mensaje no enviado. Ningún operador lo escribe jamás y, en consecuencia, no dispara ningún evento de webhook; nada notifica a un suscriptor de una cancelación. ConsultaGET /messages/:idsi expones la cancelación en tu propia interfaz y necesitas observarla. - Sandbox / modo de prueba. Los envíos de prueba se resuelven en
test_sentantes de cualquier despacho al proveedor. Disparanmessage.sentconstatus: "test_sent"ymetadata.test_mode: true, así que un suscriptor debe ramificar enmetadata.test_modepara mantener el tráfico de sandbox fuera del tratamiento de producción.
En qué ramificar
Las integraciones deben conmutar solo en campos legibles por máquina, nunca en etiquetas de presentación:status— el estado actual del mensaje. Este es el punto de ramificación principal. Gestiona el conjunto completo: además de los estados del camino feliz y de fallo comunes, no olvidescancelled,test_sent,submitted_no_receiptybounced.metadata.classified_error_code— presente en fallos terminales; la categoría de fallo normalizada y legible por máquina. Combínala con elerror_code/error_messageen crudo en webhooksmessage.failedcuando quieras la redacción del propio operador.metadata.no_dlr_channel— presente en registros de DM de Meta; te dice que unsubmitted_no_receiptes el caso Meta con entrega garantizada, no el caso SMPP ambiguo.state_class/is_terminal— en webhooks de ciclo de vida.is_terminal: true(equivalentementestate_class: "terminal") significa que el resultado es final;submitted_no_receiptinformais_terminal: falseprecisamente para que no cierres el expediente sobre él.
Errores comunes
- Tratar
message.createdcomo aceptación.message.createddispara en el momento en que el registro se pone en cola, antes de cualquier llamada al proveedor. Un rechazo síncrono (un 4xx en el envío, o unrejected/failedinmediato) deja ese evento entregado igualmente. Combínalo conmessage.sentantes de concluir que el mensaje salió. - Suponer que
deliveredes inmutable. Operadores en algunas rutas emiten un acuse de entrega y luego una corrección minutos después — algunos operadores indios y brasileños hacen esto. Orbit lo respeta: el registro puede moversedelivered → undeliveredodelivered → failed. Si copias estados en tu propio almacén de datos, aplica las actualizaciones de forma idempotente por id de mensaje en vez de ignorar transiciones de un mensaje que ya marcaste como entregado. - Esperar un webhook en
cancelled. Nunca llegará — la cancelación se origina en tu propia llamada a la API, no en un callback del operador, así que la plataforma no emite evento para ella. El registro simplemente queda encancelledhasta que lo borres. - Meter
submitted_no_receipten el saco de fallos. Llega en el tipo de eventomessage.failedpor razones de transporte (no hay evento dedicado), pero elstatusde su carga essubmitted_no_receiptconis_terminal: false. Ramifica endata.status, no en el tipo de evento, y mantenlo fuera de tus métricas de fallos duros. - Esperar un evento terminal por mensaje. Un mensaje puede emitir
message.failedconstatus: "submitted_no_receipt"y luegomessage.delivered, cuando un acuse de operador lento finalmente llega dentro de la ventana de llegada tardía. Desduplica y concilia pormessage_id, dejando que el evento posterior prevalezca.