Skip to main content

Zustellungslebenszyklus

Jede ausgehende Nachricht, die Sie über Orbit senden, trägt ein status-Feld, das fortschreitet, während die Nachricht von Ihrem API-Aufruf zum Gerät des Empfängers – oder zu einem Fehlerergebnis – wandert. Diese Seite erläutert die konzeptuelle Zustandsmaschine: was die Zustände bedeuten, was jede Transition antreibt und wo die betreiberabhängigen Ränder liegen. Lesen Sie sie, bevor Sie Ihren ersten Webhook abonnieren oder Ihre Integration auf ein Nachrichtenergebnis verzweigen. Die Zustandssemantik, die vollständige Transitionstabelle und die Webhook-Ereigniszuordnung leben in der Referenz zum Nachrichtenstatus-Lebenszyklus; das Antwortschema zum Lesen des aktuellen Status einer Nachricht steht in der Messaging-API-Referenz. Diese Seite verbindet beides auf einer höheren Ebene.

Der Glückspfad

Eine Nachricht, die Ende zu Ende erfolgreich ist, durchläuft: pending → queued → sending → sent → delivered → read Jede Transition wird von einer anderen Komponente angetrieben – keine einzelne Partei sieht den gesamten Weg: Zwei Komponenten sitzen neben diesem Pfad und können einen Datensatz daraus entfernen:
  • No-DLR-Planer. Wenn ein Carrier nie eine Zustellbestätigung zurücksendet, überträgt der Planer sent nach einem kanalspezifischen Gnadenfenster zu submitted_no_receipt — siehe Carrier-bestätigt vs. intermediär am Draht.
  • Sie (der Sender). Die Stornierung einer geplanten, noch nicht gesendeten Nachricht stellt sie auf cancelled, einen Endzustand, den kein Provider-Aufruf und keine DLR berührt. Sandbox-Sendungen werden zu test_sent aufgelöst, einem Endzustand, der gemeldet wird, bevor irgendetwas beim Provider veröffentlicht wird. Das Löschen eines Datensatzes verschiebt jeden Endzustand in deleted; danach kann nichts mehr ihn berühren.
Eine für die Zukunft geplante Nachricht hält in scheduled, bis die Startzeit erreicht ist, und tritt dann der Warteschlange bei. scheduled kann nur auf drei Weisen verlassen werden: Beförderung zu queued zur Startzeit, cancelled von Ihnen, oder expired, wenn ihr Gültigkeitsfenster vor der Veröffentlichung abläuft.

Carrier-bestätigt vs. intermediär am Draht

Die wichtigste Unterscheidung für Berichte und Abgleich ist, ob ein Zustand eine carrier-bestätigte oder eine intermediäre am Draht Streitigkeit ist:
  • delivered und read sind carrier-bestätigt. Es kam eine echte DLR; der Carrier selbst hat sich auf das Ergebnis geeinigt.
  • submitted_no_receipt ist intermediär. Es bedeutet „Zum Versand angenommen, und innerhalb des gnaden Fensters wurde keine Bestätigung zurückgegeben.“ Es wird als intermediär bezeichnet (state_class: "intermediate", is_terminal: false) in jedem Webhook, weil eine echte delivered-, read- oder fehlerhafte DLR später eintreffen und es ersetzen kann.
Behandeln Sie submitted_no_receipt als unbekanntes Ergebnis, nicht als Zustellung. Ob sich „unbekannt“ zu positiv oder bloß intermediär neigt, hängt vom Kanal ab — siehe Kanalvariationen. expired ist die andere Seite der Sicht: Es kam eine DLR, aber so spät, dass das Bestätigungsfenster bereits geschlossen war. Das Ergebnis kann nicht ermittelt werden und der Datensatz ist geschlossen; expired wird an die Abonnenten als message.failed-Ereignis verteilt.

Kanalvariationen

  • Meta DM-Kanäle (Instagram, Messenger). Metas Versandoberfläche gibt niemals Zustellbestätigungen aus. Der Versand verlässt Orbit als sent, markiert no_dlr_channel in den Nachrichten-Metadaten und verfällt nach 5 Minuten in submitted_no_receipt. Bei einem optional akzeptierten Empfänger garantiert Meta die Zustellung bei der Annahme — deshalb verhält sich submitted_no_receipt auf diesen Kanälen wie ein funktionales Zustellsignal, und die Nachrichten-Metadaten tragen no_dlr_channel: true, damit Sie den Zustand unterscheiden können.
  • SMPP-unterstützte Kanäle (SMS, MMS, Voice, Fax, RCS). Das Gnadenfenster beträgt 30 Minuten. Hier ist submitted_no_receipt ein echtes intermediäres Signal: Das Gerät hat die Nachricht vielleicht ohne berichtete Bestätigung erhalten, der Carrier sendet auf dieser Route vielleicht niemals Bestätigungen, oder die Bestätigung ging vielleicht im Transit verloren. Verfolgen Sie diese Quote als ein von der Zustellquote unterschiedliches Signal — eine dauerhaft hohe submitted_no_receipt-Quote auf einem Ziel deutet auf eine schlechte Route oder eine kaputte Bestätigungsroute hin und verdient in beiden Fällen eine Untersuchung.
  • E-Mail. Fügt ein Fehlerergebnis hinzu, das reine DM-Kanäle nicht kennen: bounced, wenn der Mailserver den Empfang der Nachricht ablehnt. Bounce zählt zu Ihrer endgültigen Fehlerrate wie failed, ist aber ein getrennter Zustand, damit Sie empfängerseitige Ablehnungen von providerseitigen trennen können.
  • Senderstornierung. cancelled wird nur von Ihnen gemeldet — über POST /messages/:id/cancel auf einer noch nicht gesendeten Nachricht. Kein Carrier schreibt es jemals, und als Folge löst es kein Webhook-Ereignis aus; nichts informiert den Abonnenten über die Stornierung. Rufen Sie GET /messages/:id ab, wenn Sie die Stornierung in Ihrer Oberfläche anzeigen und beobachten müssen.
  • Sandbox / Testmodus. Testsendungen werden vor jeder Provider-Veröffentlichung zu test_sent aufgelöst. Sie lösen message.sent mit status: "test_sent" und metadata.test_mode: true aus, daher muss der Abonnent auf metadata.test_mode verzweigen, um Sandbox-Verkehr aus der Produktionsverarbeitung herauszuhalten.

Wonach verzweigt wird

Integrationen müssen nur auf maschinell lesbare Felder verzweigen, niemals auf angezeigte Beschreibungen:
  • status — der aktuelle Zustand der Nachricht. Dies ist der primäre Verzweigungspunkt. Behandeln Sie die Menge als Ganzes: neben den Glückspfad- und Fehlerzuständen, vergessen Sie nicht cancelled, test_sent, submitted_no_receipt und bounced.
  • metadata.classified_error_code — auf endgültigen Fehlerergebnissen vorhanden; die maschinell definierte, maschinell aktivierte Fehlerkategorie. Kombinieren Sie sie mit dem rohen error_code / error_message im message.failed-Webhook, wenn Sie die Worte des Carriers wollen.
  • metadata.no_dlr_channel — auf Meta-DM-Datensätzen vorhanden; sagt Ihnen, dass submitted_no_receipt der von Meta garantierte Zustellzustand ist, nicht der aus SMPP übersetzte.
  • state_class / is_terminal — auf dem Lebenszyklus-Webhook. is_terminal: true (gepaart mit state_class: "terminal") bedeutet, dass das Ergebnis endgültig ist; submitted_no_receipt meldet genau deshalb is_terminal: false, damit Sie das Buch nicht darauf schließen.

Häufige Fehler

  1. message.created als Annahme behandeln. message.created feuert in dem Moment, in dem der Datensatz der Warteschlange zugefügt wird, noch vor jedem Provider-Aufruf. Eine synchrone Ablehnung (4xx beim Senden oder sofortiges rejected/failed) lässt dieses Ereignis trotzdem ausliefern. Kombinieren Sie es mit message.sent, bevor Sie schließen, dass die Nachricht unterwegs ist.
  2. Annehmen, delivered sei unveränderlich. Carrier auf manchen Routen geben eine Zustellbestätigung aus und korrigieren sie Minuten später — einige indische und brasilianische Carrier tun das. Orbit respektiert das: Ein Datensatz kann von delivered → undelivered oder von delivered → failed übergehen. Wenn Sie Zustände in Ihrem eigenen Datenspeicher spiegeln, wenden Sie Aktualisierungen idempotent per Nachrichten-ID an, statt Transitionen für eine bereits vollendete Nachricht zu verwerfen.
  3. Auf einem Webhook bei cancelled warten. Er wird niemals kommen — die Stornierung ist an Ihren API-Aufruf gebunden, nicht an einen Carrier-Aufruf, daher gibt die Plattform dafür kein Ereignis aus. Der Datensatz sitzt einfach in cancelled, bis Sie ihn löschen.
  4. submitted_no_receipt in den Fehlerstrom werfen. Es kommt aus Klassifizierungsgründen als Ereignistyp message.failed (keine separate Route), aber branch auf data.status, nicht auf den Ereignistyp, und begrenzen Sie es von harten Fehlertönen ab.
  5. Ein endgültiges Ereignis pro Nachricht erwarten. Eine Nachricht kann message.failed mit status: "submitted_no_receipt" ausgeben und danach message.delivered, wenn die langsame Carrier-Bestätigung schließlich im Nachlauffenster eintrifft. Aktualisieren und deduplizieren Sie über die message_id, und lassen Sie das spätere Ereignis gewinnen.
Sobald die Zustandsmaschine klar ist, stehen die Ereignis-Payloads je Zustand in der Webhook-Ereignis-Referenz und die Zustandssemantik allein in der Referenz zum Nachrichtenstatus-Lebenszyklus.