Ciclo de vida de entrega
Cada mensaje saliente que envías a través de Orbit lleva un campostatus que avanza mientras el mensaje viaja desde tu llamada de API hacia el dispositivo del destinatario — o hacia un resultado de fallo. Esta página explica la máquina de estados conceptual: qué significan los estados, qué impulsa cada transición y dónde están los bordes dependientes del operador. Léela antes de suscribirte a tu primer webhook o de ramificar tu integración según el resultado de un mensaje.
La semántica de estados, la tabla completa de transiciones y el mapa de eventos de webhook viven en la referencia del ciclo de vida del estado de mensajes; el esquema de respuesta para leer el estado actual de un mensaje está en la referencia de la API de mensajería. Esta página conecta ambos a un nivel superior.
El camino feliz
Un mensaje que triunfa de extremo a extremo recorre:pending → queued → sending → sent → delivered → read
Cada transición la impulsa un componente distinto — ninguna parte ve todo el camino:
Dos componentes se sientan al lado de este camino y pueden sacar un registro de él:
- El planificador de no-DLR. Cuando un operador nunca devuelve un acuse de entrega, el planificador envejece
sentasubmitted_no_receiptdespués de una ventana de gracia propia del canal — consulta Confirmado por el operador vs. intermediario en el hilo. - Tú (el remitente). Cancelar un mensaje programado que aún no se envió lo fija en
cancelled, un estado terminal que ninguna llamada del proveedor ni ningún DLR toca. Los envíos de sandbox se resuelven atest_sent, un estado terminal que se reporta antes de cualquier publicación al proveedor. Eliminar un registro mueve cualquier estado terminal adeleted; después, nada puede tocarlo.
scheduled hasta la hora de lanzamiento y luego se une a la cola. scheduled solo puede salir por tres caminos: promoción a queued a la hora de lanzamiento, cancelled por tu parte, o expired si su ventana de validez vence antes de la publicación.
Confirmado por el operador vs. intermediario en el hilo
La distinción más importante para informes y conciliación es si un estado es un resultado confirmado por el operador o una disputa intermediaria en el hilo:deliveredyreadestán confirmados por el operador. Llegó un DLR real; el propio operador zanjó el resultado.submitted_no_receiptes intermediario. Significa «aceptado para envío, y ningún acuse volvió dentro de la ventana de gracia». Se describe como intermediario (state_class: "intermediate",is_terminal: false) en cada webhook, porque un DLR real dedelivered,reado fallo puede llegar más tarde y sustituirlo.
submitted_no_receipt como un resultado desconocido, no como una entrega. Que lo «desconocido» se incline a positivo o a mero intermediario depende del canal — consulta Variaciones por canal.
expired es la vista por el otro lado: llegó un DLR, pero tan tarde que la ventana del acuse ya se había cerrado. El resultado no puede conocerse y el registro está cerrado; expired se distribuye a los suscriptores como un evento message.failed.
Variaciones por canal
- Canales DM de Meta (Instagram, Messenger). La superficie de envío de Meta nunca emite acuses de entrega. El envío sale de Orbit como
sent, marcano_dlr_channelen los metadatos del mensaje y decae asubmitted_no_receipttras 5 minutos. Dado que en un destinatario aceptado opcionalmente Meta garantiza la entrega en la aceptación, 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 el estado. - Canales servidos por SMPP (SMS, MMS, voz, fax, RCS). La ventana de gracia es de 30 minutos. Aquí
submitted_no_receiptes un intermediario real: el dispositivo pudo recibir el mensaje sin acuse reportado, el operador puede no enviar nunca acuses en esa ruta, o el acuse pudo caerse en tránsito. Sigue esta proporción como una señal distinta de la proporción de entregas — una proporción asentada desubmitted_no_receipten un destino señala una ruta no atendida o una ruta de acuse rota, y merece investigación en cualquiera de los dos casos. - Correo electrónico. Añade un resultado de fallo que los canales puros de DM no tienen:
bounced, cuando el servidor de correo de recepción rechaza el mensaje. El rebote cuenta contra tu tasa de fallos finales igual quefailed, pero es un estado separado que te permite separar los rechazos del lado del destinatario de los del lado del proveedor. - Cancelación del remitente.
cancelledsolo la reportas tú — a través dePOST /messages/:id/cancelsobre un mensaje aún no enviado. Ningún operador la escribe jamás, y en consecuencia no dispara ningún evento de webhook; nada informa al suscriptor de la cancelación. Llama aGET /messages/:idsi expones la cancelación en tu interfaz y necesitas observarla. - Sandbox / modo de prueba. Los envíos de prueba se resuelven a
test_sentantes de cualquier publicación al proveedor. Disparanmessage.sentconstatus: "test_sent"ymetadata.test_mode: true, por lo que el suscriptor debe ramificar sobremetadata.test_modepara mantener el tráfico de sandbox fuera del procesamiento de producción.
Sobre qué ramificar
Las integraciones deben ramificar solo sobre campos legibles por máquina, nunca sobre descripciones mostradas:status— el estado actual del mensaje. Es el punto de ramificación primario. Trata el conjunto completo: junto a los estados del camino feliz y el resultado de fallo, no olvidescancelled,test_sent,submitted_no_receiptybounced.metadata.classified_error_code— presente en los resultados finales de fallo; la categoría de fallo definida por máquina y habilitada para máquina. Combínala con elerror_code/error_messagecrudo del webhookmessage.failedcuando quieras las palabras del operador.metadata.no_dlr_channel— presente en los registros DM de Meta; te dice quesubmitted_no_receiptes el estado de entrega garantizado por Meta, no el intermediario traducido desde SMPP.state_class/is_terminal— en el webhook del ciclo de vida.is_terminal: true(emparejado constate_class: "terminal") significa que el resultado es final;submitted_no_receiptreportais_terminal: falseprecisamente para que no cierres el libro sobre él.
Errores comunes
- Tratar
message.createdcomo la aceptación.message.createdse dispara en el momento en que el registro se pone en cola, antes de cualquier llamada al proveedor. Un rechazo sincrónico (4xx al enviar, o unrejected/failedinmediato) deja ese evento entregado igualmente. Combínalo conmessage.sentantes de concluir que el mensaje salió. - Suponer que
deliveredes inmutable. Los operadores en algunas rutas emiten un acuse de entrega y luego una corrección minutos después — algunos operadores indios y brasileños lo hacen. Orbit lo respeta: un registro puede pasar dedelivered → undeliveredo dedelivered → failed. Si espejas estados en tu propio almacén de datos, aplica las actualizaciones de forma idempotente por id de mensaje en lugar de desechar transiciones para un mensaje ya servido. - Esperar un webhook sobre
cancelled. Nunca llegará — la cancelación está ligada a tu llamada de API, no a una llamada del operador, así que la plataforma no emite un evento para ella. El registro simplemente permanece encancelledhasta que lo elimines. - Tirar
submitted_no_receiptal flujo de fallos. Llega como tipo de eventomessage.failedpor razones de clasificación (no hay ruta aparte), pero ramifica sobredata.status, no sobre el tipo de evento, y sepáralo de los tonos de fallo duros. - Esperar un único evento final por mensaje. Un mensaje puede emitir
message.failedconstatus: "submitted_no_receipt"y luegomessage.delivered, cuando el acuse lento del operador finalmente llega dentro de la ventana de llegada tardía. Actualiza y deduplica pormessage_id, y deja que el evento más reciente gane.