Skip to main content

Ciclo de vida de entrega

Cada mensaje saliente que envías a través de Orbit lleva un campo status 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 sent a submitted_no_receipt despué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 a test_sent, un estado terminal que se reporta antes de cualquier publicación al proveedor. Eliminar un registro mueve cualquier estado terminal a deleted; después, nada puede tocarlo.
Un mensaje programado para el futuro se detiene en 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:
  • delivered y read están confirmados por el operador. Llegó un DLR real; el propio operador zanjó el resultado.
  • submitted_no_receipt es 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 de delivered, read o fallo puede llegar más tarde y sustituirlo.
Trata 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, marca no_dlr_channel en los metadatos del mensaje y decae a submitted_no_receipt tras 5 minutos. Dado que en un destinatario aceptado opcionalmente Meta garantiza la entrega en la aceptación, en estos canales submitted_no_receipt se comporta como una señal funcional de entrega, y los metadatos del mensaje llevan no_dlr_channel: true para 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_receipt es 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 de submitted_no_receipt en 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 que failed, 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. cancelled solo la reportas tú — a través de POST /messages/:id/cancel sobre 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 a GET /messages/:id si expones la cancelación en tu interfaz y necesitas observarla.
  • Sandbox / modo de prueba. Los envíos de prueba se resuelven a test_sent antes de cualquier publicación al proveedor. Disparan message.sent con status: "test_sent" y metadata.test_mode: true, por lo que el suscriptor debe ramificar sobre metadata.test_mode para 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 olvides cancelled, test_sent, submitted_no_receipt y bounced.
  • 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 el error_code / error_message crudo del webhook message.failed cuando quieras las palabras del operador.
  • metadata.no_dlr_channel — presente en los registros DM de Meta; te dice que submitted_no_receipt es 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 con state_class: "terminal") significa que el resultado es final; submitted_no_receipt reporta is_terminal: false precisamente para que no cierres el libro sobre él.

Errores comunes

  1. Tratar message.created como la aceptación. message.created se 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 un rejected/failed inmediato) deja ese evento entregado igualmente. Combínalo con message.sent antes de concluir que el mensaje salió.
  2. Suponer que delivered es 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 de delivered → undelivered o de delivered → 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.
  3. 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 en cancelled hasta que lo elimines.
  4. Tirar submitted_no_receipt al flujo de fallos. Llega como tipo de evento message.failed por razones de clasificación (no hay ruta aparte), pero ramifica sobre data.status, no sobre el tipo de evento, y sepáralo de los tonos de fallo duros.
  5. Esperar un único evento final por mensaje. Un mensaje puede emitir message.failed con status: "submitted_no_receipt" y luego message.delivered, cuando el acuse lento del operador finalmente llega dentro de la ventana de llegada tardía. Actualiza y deduplica por message_id, y deja que el evento más reciente gane.
Cuando la máquina de estados esté clara, las cargas útiles de eventos por estado están en la referencia de eventos de webhook, y la semántica de estados por sí sola en la referencia del ciclo de vida del estado de mensajes.