Skip to main content

Ciclo de vida de entrega

Todo mensaje saliente que envías a través de Orbit lleva un campo status 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 sent a submitted_no_receipt tras 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 en test_sent, un estado terminal alcanzado antes de cualquier despacho al proveedor. Borrar un registro mueve cualquier estado terminal a deleted, tras lo cual nada puede tocarlo.
Un mensaje programado para el futuro espera en 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:
  • delivered y read están confirmados por operador. Llegó un DLR real; el propio operador afirmó el resultado.
  • submitted_no_receipt es 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 de delivered, read o fallo genuino puede llegar después y sobrescribirlo.
Trata 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 marca no_dlr_channel en los metadatos del mensaje y pasa a submitted_no_receipt a los 5 minutos. Para un destinatario con opt-in, Meta garantiza la entrega en la aceptación — así que 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 ese caso.
  • Canales con respaldo SMPP (SMS, MMS, voz, fax, RCS). La ventana de gracia es de 30 minutos. Aquí submitted_no_receipt es 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 de submitted_no_receipt en 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 que failed, pero es un estado distinto para que puedas separar rechazos del lado del destinatario de los del lado del proveedor.
  • Cancelación del operador. cancelled solo es alcanzable por ti — mediante POST /messages/:id/cancel en 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. Consulta GET /messages/:id si expones la cancelación en tu propia interfaz y necesitas observarla.
  • Sandbox / modo de prueba. Los envíos de prueba se resuelven en test_sent antes de cualquier despacho al proveedor. Disparan message.sent con status: "test_sent" y metadata.test_mode: true, así que un suscriptor debe ramificar en metadata.test_mode para 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 olvides cancelled, test_sent, submitted_no_receipt y bounced.
  • metadata.classified_error_code — presente en fallos terminales; la categoría de fallo normalizada y legible por máquina. Combínala con el error_code / error_message en crudo en webhooks message.failed cuando quieras la redacción del propio operador.
  • metadata.no_dlr_channel — presente en registros de DM de Meta; te dice que un submitted_no_receipt es el caso Meta con entrega garantizada, no el caso SMPP ambiguo.
  • state_class / is_terminal — en webhooks de ciclo de vida. is_terminal: true (equivalentemente state_class: "terminal") significa que el resultado es final; submitted_no_receipt informa is_terminal: false precisamente para que no cierres el expediente sobre él.

Errores comunes

  1. Tratar message.created como aceptación. message.created dispara 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 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. 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 moverse delivered → undelivered o delivered → 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.
  3. 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 en cancelled hasta que lo borres.
  4. Meter submitted_no_receipt en el saco de fallos. Llega en el tipo de evento message.failed por razones de transporte (no hay evento dedicado), pero el status de su carga es submitted_no_receipt con is_terminal: false. Ramifica en data.status, no en el tipo de evento, y mantenlo fuera de tus métricas de fallos duros.
  5. Esperar un evento terminal por mensaje. Un mensaje puede emitir message.failed con status: "submitted_no_receipt" y luego message.delivered, cuando un acuse de operador lento finalmente llega dentro de la ventana de llegada tardía. Desduplica y concilia por message_id, dejando que el evento posterior prevalezca.
Una vez claro el autómata de estados, las cargas de webhook por estado están en la referencia de eventos de webhook, y la semántica de estados en el nivel de endpoint está en la referencia del ciclo de vida de estados de mensajes.