Enable and Operate the SMS Double Opt-In Flow
Double opt-in (confirmed consent) holds your first marketing SMS to a contact with no consent record on file and sends a one-time confirmation prompt instead. The contact receives your marketing copy only after they reply YES. This guide walks a US-style marketing tenant through enabling the flow, watching each state land, and reporting on held sends. For the regulatory posture and the per-contact handshake API, see Confirmed Consent (Double Opt-In) Handshakes and Consent Management. This page covers the operational loop: the setting, the state machine, the prompt, the skips, the reply, and the audit trail.Double opt-in is a tenant-owned control. It is off by default, and
Orbit never enables it, prompts a contact, or confirms consent except
where your setting or your API calls say to. Whether your program must
require confirmed consent is your determination; this page shows how
to operate the control, not when the law requires it, and is not
legal advice.
1. When to enable it
Enable the flow where your marketing program is graded on confirmed consent:- US and Canadian marketing traffic. The CTIA Short Code Monitoring Handbook (§5.1.3 and §5.2) grades short code and 10DLC campaigns on verified affirmative consent: a recorded prompt followed by a recorded affirmative reply. Carriers reviewing campaign attestations expect to see exactly that pattern, and short code programs are monitored most closely.
- Launches headed for campaign review. A prompt plus a recorded reply is stronger opt-in evidence than a bare web-form capture, and it is the evidence tier 10DLC and short code reviews ask for.
- Most of the EU/UK under GDPR, where legitimate interest or an existing soft opt-in exception covers the send.
- Australia under the Spam Act, where a prior business relationship plus a working unsubscribe path suffices.
- Any audience where consent you already hold (imported or collected at signup) satisfies the local regime; see Country Requirements for per-jurisdiction posture.
2. The consent state machine
Every (contact, channel) pair reads as one of four states:
How the states map onto rows in your consent ledger:
- No record is simply the absence of a consent row for the pair.
- Pending is a row with consent type
sms_double_opt_instampedawaiting_yesat prompt time. It records that the contact was prompted; it is not a grant, and while it is the pair’s only row, marketing stays held. - Confirmed is an
opted_ingrant row written when the reply arrives. The original prompt row stays in the ledger, stamped confirmed, so the pair carries two dated events: prompted at, and confirmed at.
3. Enable it
The flow reads three keys in your organization settings:sms_double_opt_in.enabled (the gate), sms_double_opt_in.brand_name
(up to 32 characters, embedded in the prompt), and
sms_double_opt_in.help_contact (up to 64 characters, appended as a
Help: line when it fits the single-SMS budget).
From the dashboard
Open Settings → Compliance → SMS double opt-in, flip the switch, set the brand name and help contact if you want them, review the live preview (it renders exactly what the recipient will receive), and choose Save changes.From the API
The same keys go through the organization settings endpoint:enabledmust be the literal booleantrue. The string"true"leaves the flow off, because the gate checks strict equality.- The settings merge replaces the whole
sms_double_opt_inobject, so send all three keys every time; sending onlyenabledclears your branding.
enabled: false. Held
sends are not released retroactively; re-send the campaign when you are
ready.
4. The confirmation prompt, and customizing it
The shipped prompt follows the CTIA §5.2 confirmation format: program identification, the affirmative act, the rates disclosure, frequency language, and the STOP instruction.Reply YES to subscribe to Acme alerts. Msg & data rates may apply. Msg freq varies. Reply STOP to opt out. Help: help@acme.com.Customization is the two setting keys, and deliberately only those:
brand_namereplaces the generic service reference. Keep it at or under 32 characters.help_contactappends theHelp:sentence when the branded body still fits one SMS segment (160 GSM-7 characters). If your branding pushes the body over one segment, the prompt falls back to a shorter unbranded body so the rates disclosure and STOP instruction are never dropped.
5. While pending: marketing sends are held, not failed
After the first marketing send is rewritten to the prompt, further marketing sends to the same contact short-circuit before they reach a carrier:- The send returns the message with
status: "skipped"andreason: "pending_consent". This is a soft skip in the same family as quiet-hours and suppression skips, not the 422 an opted-out recipient produces; batch and campaign jobs continue past the contact. - Campaign reports show the recipient as skipped with that reason: not delivered, not failed. A held send is not billed as a delivered message.
- Because the message never reaches a carrier, no delivery webhook
follows it. The skipped result is the whole record of the attempt, so
do not build alerting that treats
pending_consentas a delivery failure; it is the flow working as designed.
metadata.message_type = "marketing") are ever held or rewritten.
6. Inbound YES: how the reply unblocks the contact
When the contact replies YES, START, or SUBSCRIBE, the standard inbound keyword handling writes theopted_in grant and stamps the pending row
confirmed. You wire nothing extra: the same matcher that honors STOP
for opt-outs recognizes the affirmative side, and the next marketing
send delivers as written.
Multilingual replies are honored. The matcher recognizes affirmative
keywords in English, Turkish, German, Spanish, French, Portuguese,
Italian, Arabic, and Dutch (JA, OUI, SÍ, SIM, EVET and the rest), and it
normalises casing, invisible characters, and trailing punctuation, so
yes. and YES confirm exactly as YES does. Opt-out keywords win
any tie: a STOP is always honored over an affirmative read of the same
text. If you relay replies yourself through
POST /compliance/consent/double-opt-in/confirm, you may pass a lang
hint to pin the keyword set; see
Confirmed Consent (Double Opt-In) Handshakes.
A reply that matches nothing (say, “maybe later”) changes nothing: the
contact stays pending.
7. Audit: reading the evidence back
- Per contact: open Audience → Contacts → the contact → Consent
log. You see the pending prompt row (prompted-at, and the send that
triggered it) and the
opted_ingrant (confirmed-at, source of the reply) as separate entries. - Tenant-wide: the Consent ledger lists grants and their states, including the Expiring view for time-bounded grants.
- Exports: the consent export gives you ledger rows over a window; use it to show that for any marketed contact, a prompt event precedes the grant event. See Consent Management.
- Evidence packages: the evidence binder assembles consent history with the rest of your compliance evidence for a regulator or carrier discovery request. See Evidence Binder.
8. Common failure modes
The contact never replies. The pair stays pending indefinitely; marketing stays held and nothing auto-sends, and the prompt does not expire on its own. Re-issuing the send is safe: the contact is prompted once, and later attempts skip withpending_consent. To re-engage the
contact, use another channel or a re-permission campaign per
Migrating Consent.
The contact replies STOP while pending. Opt-out wins. The pair
moves to opted out, marketing is blocked, and the usual STOP
acknowledgement goes out. A later YES writes a fresh grant that
supersedes the opt-out.
The contact replies with an unrecognized phrase. State is
unchanged; the contact remains pending and the next marketing attempt
skips with pending_consent again.
You enabled the flow over an already opted-in list. Nothing changes
for those contacts. Any existing grant satisfies the gate, so confirmed
contacts are neither prompted nor held; only pairs with no consent
record receive a prompt.
Migrating existing opted-in lists onto the flow. Import the consent
you already hold before or as you enable the flow; imported grants
satisfy the gate, so proven contacts are never prompted. The migration
mechanics live in
Migrating Consent.
The prompt delivered but replies never confirm. Check that the
replying number is one Orbit routes (inbound keyword handling only sees
messages that arrive on your numbers) and that the reply comes from the
same number that received the prompt: the YES is matched to the contact
by the number it arrives from.
Worked example: US marketing tenant, end to end
Acme Runs, a US short code program, turns the flow on and sends a campaign:- In Settings → Compliance → SMS double opt-in: switch on, brand
name
Acme Runs, help contacthelp@acmeruns.com, save. (ThePUT /settings/generalcall from §3 does the same.) - The campaign sends a marketing SMS to
+14155550101, a contact with no consent record. The send is rewritten: the contact receivesReply YES to subscribe to Acme Runs alerts. Msg & data rates may apply. Msg freq varies. Reply STOP to opt out. Help: help@acmeruns.com.and the contact’s Consent log gains a pending row. - A second campaign fires before the contact answers. That recipient’s
result is
status: "skipped",reason: "pending_consent"; everyone else in the audience receives the campaign. - The contact texts
Yes.Inbound keyword handling writes theopted_ingrant and stamps the pending row confirmed; the Consent log now shows both dated events. - The next campaign delivers the real creative to that contact.