Keep one-time codes out of the Inbox
A one-time code is not a conversation: nobody replies to it, nobody triages it, nobody should have it mint a new-conversation bell per destination. Yet by default every absorbed SMPP submit — and any other send — upserts a per-contact Inbox conversation and notifies your agent queue the first time a destination appears. For a tenant firing thousands of OTP delivers at fresh numbers, that is one bell and one email per code. Transactional no-inbox (shipped 2026-09-03) is a gate on the outbound conversation upsert. When the gate trips, the send still produces a full per-channel Messages row — the record of what left the platform, its status trail, cost attribution, and DLRs are untouched — but it does not create or append an Inbox conversation and does not mint theinbox_new_conversation notification. If the recipient ever replies, the reply opens a thread on the normal inbound path and the conversation appears then, when there is something to answer.
Everything here is tenant-owned configuration. The gate has two independent triggers — one explicit, one inferred — and you can nail the behaviour down per termination rule.
1. The two triggers
The gate respects two ways of saying “this send is transactional”:- Explicit on the termination rule. A
deliverhop in a termination rule can declareinbox: 'none'. When that hop wins, the send is stamped as no-inbox and the conversation is skipped regardless of the template’s category. - Implicit by template category. When the resolved WhatsApp template’s category is AUTHENTICATION (Meta’s OTP category), the send is treated as transactional even with no rule saying so. A code, by definition, is not a conversation.
inbox: 'thread' on the hop beats the inference: if you deliver on an AUTHENTICATION template but genuinely want a thread and a bell, say so and the conversation switches back on. Within metadata, inbox: 'thread' likewise beats the AUTHENTICATION inference — explicit intent always wins over the guess.
The resolution order, in words:
The order matters when signals disagree. A rule that says
thread on a hop whose template is AUTHENTICATION produces a conversation; a rule that says none on a MARKETING template produces none.
2. Pinning behaviour on a termination rule
Termination rules decide what an inbound submit (SMPP or otherwise) terminates to — adeliver hop to a channel, or an absorb that takes ownership — with per-hop fields for channel, account, template, price ceiling, and now inbox participation.
The inbox field sits on each deliver hop, alongside channel, account, and on_failure:
inbox: 'none'— this hop is machine traffic: record the Messages row, skip the conversation, skip the bell.inbox: 'thread'— this hop is a conversation: do the full upsert even if the template is AUTHENTICATION.inboxomitted — infer from the template category (AUTHENTICATION → skip; anything else → normal).
inbox selector to a deliver hop when you build or edit the rule, or POST the JSON above against the rules endpoint. A shadow-mode rule (mode: "shadow") evaluates and records what it would have done without changing live behaviour; flip to enforce once the shadow trace matches your expectation.
Because the gate keys off the rule’s hop rather than the send’s body, routing still behaves the same when the OTP originates from an ESME bind — the submit is absorbed, the terminator dispatches the hop, and the hop carries the stamp into the send.
3. What does NOT change
The gate is deliberately narrow. Four behaviours are untouched and worth pinning down because they are where tenants most often expect side effects:- Inbound replies still open threads. The gate sits on the outbound upsert only. An inbound message from the contact arrives on the normal inbound path and opens the conversation there — whether or not the outbound code suppressed one. If the recipient texts back “what?”, you get a thread the moment it matters.
- The Messages list still shows every row. Per-channel Messages (SMS, WhatsApp, email, RCS) records the send with its full status trail and cost attribution. Suppression removes the conversation object, not the message record — so analytics, per-message compliance receipts, and the per-channel list stay complete.
- Receipts / DLR mode are unaffected.
dlrMode: bind | webhook | bothon the credential governs where delivery receipts land; the inbox gate changes nothing about receipts. See the SMPP guide and the two-plane DLR model under the delivery-receipt model and send-side receipts. - Failed sends were already excluded. A synchronously rejected send has never bumped conversation ordering (it never reached the customer); the new gate joins that existing arm rather than replacing it. Nothing about failure handling erodes.
4. Two example flows
Flow A — default behaviour, no rule
You deliver a WhatsApp template of category AUTHENTICATION (verify_user) to a fresh destination with no rule in play. The template’s category resolves at send time, the gate infers transactional, and the send completes with a Messages row and no Inbox conversation, no inbox_new_conversation. Agents see nothing until the recipient replies.
The same send on a MARKETING template behaves exactly as before — conversation upserted, first-destination bell fires — because the inference only trips on AUTHENTICATION.
Flow B — pinned explicit on the rule
Your SMPP-originated OTP traffic terminates on WhatsApp through the rule from section 2. The hop carriesinbox: 'none', so even a tenant whose AUTHENTICATION inference was over-broad (say, a legacy “AUTHENTICATION” template they actually use as a conversation opener) gets deterministic behaviour: no conversation, no bell, Messages row recorded, DLRs follow the bind as normal.
The expected queue effect: the agent queue stops accumulating a synthetic “conversation per code” and the first-destination notification rate drops to genuinely fresh inbound traffic — one bell per real person reaching out, not per OTP submitted. (The exact delta depends on how much of your outbound mix is OTP; measure it in the Inbox notifications panel before and after flipping enforce.)
To pin the same behaviour ad hoc — without a rule — stamp the send yourself:
metadata.inbox accepts the same two values; 'none' and 'thread' mean exactly what the rule hop means.
5. Edge cases worth knowing
Rule removed mid-flight. The stamp is applied when the terminator dispatches the hop — the send then proceeds on its own withmetadata.inbox already resolved. Deleting or editing the rule after dispatch does not retro-convert a completed send (the Messages row stands either way); it only changes what subsequent submits see. In-flight absorbed intents were claimed at dispatch time, so a rule flip mid-flurry doesn’t strand traffic.
AI-agent grounding. If your AI agent depends on a conversation object to ground an OTP follow-up (“the user just got a code, help them finish”), note that the gate suppresses the conversation upsert and the notification, not the message row. The message is still readable via the Messages API and the per-channel list; the agent’s retrieval path on messages is unaffected — but if anything in your flow assumed a conversation id exists for every send, that assumption was always wrong for failed sends and is now also wrong for transactional sends. Ground on the message, not the conversation.
Inbox team-performance metrics. Suppressed sends never enter the Inbox queue, so they do not count as unassigned/new conversations in team-performance reporting. That is the point (the queue noise was false), but if your KPI baseline counted OTP-driven “new conversations” as inbound volume, expect the new-conversation count to drop once enforce flips on — recalibrate alert thresholds against real inbound.
First-destination semantics. The inbox_new_conversation notification fires on a fresh thread. A suppressed send to a contact who already has a thread behaves identically to a suppressed send to a stranger — no notification either way — because the gate short-circuits before the freshness check matters.
6. Troubleshooting: “I still see one conversation per OTP”
Work the gate in order — the two triggers plus the two exceptions cover every report of this shape:- If you pinned the rule: read the rule back and confirm the
deliverhop actually persistedinbox: 'none'. A rule saved without the field falls through to inference, and inference only skips AUTHENTICATION templates. GET the rule and check the hop:
- If you rely on inference: confirm the template’s category resolved to AUTHENTICATION on WhatsApp. Meta categories are per-template, not per-send; a MARKETING or UTILITY template never suppresses. Check the category in the WhatsApp template manager, or with a literal
metadata.inbox: 'thread'stamp if you want the thread despite the category. - If the stamps look right but threads still appear: check whether the thread came from an inbound reply (expected — reply paths always thread). The gate only suppresses the outbound upsert; an inbound text from the recipient opens a real conversation.
- Shadow mode confusion: a
mode: "shadow"rule evaluates and records without changing behaviour. If the dashboard shows the rule “matched” but OTP conversations continue, the rule is in shadow — flip toenforce.