OpenSequence
DocsAPI referenceSDKs and CLIReference
Contacts

Consent and suppressions

SMS consent, do-not-contact, the hosted opt-out link, STOP, bounces and complaints, and where each one is enforced.

Edit on GitHub
curl
# consent needs an admin key
curl -X PUT $SEQ/v1/contacts/p_7/sms-consent -H "Authorization: Bearer $ADMIN_KEY" -H "Content-Type: application/json" \
  -d '{"consent":true,"source":"web form","at":"2026-10-01T09:12:00Z"}'
# a do-not-contact recorded in your app
curl -X POST $SEQ/v1/contacts/p_7/events -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"id":"dnc-p_7-2026-10-13","type":"suppressed","reason":"do_not_contact"}'

contacts.sms_consent is false by default and can only be set with an admin-scoped key (PUT /v1/contacts/{id}/sms-consent, with source and at for your records). No text leaves for a contact without it: the SMS executor skips the step with no_consent, and the contact.sms_consent fact is there for conditions that route non-consenting contacts elsewhere. The other SMS gates, also not configurable: a phone, a country in settings.sms_countries (default ["US"]), a known recipient time zone, and 08:00 to 21:00 in it.

Suppressions

A suppression is a mark on a contact for one channel (email, sms, linkedin) or all, with a reason. It is checked at enrollment (all suppressions exclude the contact from the batch with suppressed:<reason>) and again at execution (an email step skips with suppressed when the contact is suppressed on email or all; the same for SMS). GET /v1/contacts/{id} lists them.

SourceChannelReasonAlso
The hosted opt-out link in an email ({{ unsubscribe_url }}, List-Unsubscribe headers)emailopt_outexits the enrollment with opt_out
A STOP text (STOP, QUIT, END, CANCEL, UNSUBSCRIBE, OPT OUT, STOPALL)smsopt_outexits
A hard bounceemailhard_bounceexits with bounced; a soft bounce suppresses nothing
A complaintemailcomplaintexits with bounced
Your report { type: "opt_out", channel } (channel defaults to all)the channelopt_outexits
Your report { type: "suppressed", reason }allyour reason (do_not_contact, bad_number, invalid_email, left_company, …)exits with suppressed

Suppressions are never removed by the API today; erase the contact and create it again if a suppression was wrong.

Every email body should contain {{ unsubscribe_url }} (publishing without it is the warning NO_UNSUBSCRIBE). It renders a signed link on your instance, SEQ_PUBLIC_URL/u/{token}, valid for 90 days, that shows a one-click page; the click (or the List-Unsubscribe-Post one-click that mail clients send) writes the email suppression and exits the enrollment. Every email also carries List-Unsubscribe and List-Unsubscribe-Post headers pointing at the same token. The token is derived from SEQ_ENCRYPTION_KEYS; rotating the first key invalidates links already sent.

What exits, what pauses

SignalEffect on the contact's active enrollments
opt-out, hard bounce, complaint, suppressionexits, whatever the definition says
reply, connected callexits when exit_on contains reply; otherwise may wake a wait_for
meeting bookedexits when exit_on contains meeting_booked
out-of-office auto-replypauses until the stated return date or 7 days; never exits
sender channel credentials_neededthe step is deferred; the enrollment waits for the reconnect

The service only sees its own channels: replies in threads it started, bounces, STOP texts, unsubscribe clicks, out-of-office messages. Everything in your world (a meeting booked in another tool, a do-not-contact flag in the CRM, a person who left) must be reported, or the service keeps emailing people your users already excluded.

On this page