Unsubscribes
Suppression is a first-class list, not a flag on a contact. Every opt-out has a scope:
| Scope | Blocks |
|---|---|
| All email | Every send path below. |
| One segment | Only broadcasts to that segment. The address stays mailable everywhere else, and the contact stays a member of the segment. |
Why it is keyed by address
Opt-outs are stored per (organization, segment or all, email address) rather than per contact, because:
POST /v1/sendtakes free-form recipients that may have no contact record — in practice most suppressed addresses never had one.- The record must outlive contact deletion. Deleting someone must not resubscribe them.
Where it is enforced
| Path | Behaviour |
|---|---|
POST /v1/send | Recipients opted out of all email are dropped before the message row and before quota is spent. If all recipients were dropped, the send fails 422 and costs nothing. A partially suppressed send still reaches the survivors. Segment opt-outs do not apply: this send has no segment. |
Automation send_email | The step is recorded as skipped for an opt-out from all email. Segment opt-outs do not apply. |
| Broadcasts | Addresses opted out of all email, or of the broadcast's segment, are excluded during audience resolution. |
| Platform mail (password reset, MFA notices) | Exempt by design. A marketing opt-out must never block a password reset. |
The preference page
Every organization gets a preference page at unsubscribe.<DOMAIN>. Broadcasts fill with a link to it for each recipient. Its copy is editable from Settings → General; every field falls back to a default, so an organization that never touches it still has a working page.
The page lists, as checkboxes:
- the segment the email was sent to, ticked on arrival;
- every segment switched on with Show on the preference page that the address belongs to or already left;
- All emails.
Segment names are often internal, so a segment is listed under its public name. A segment that is not switched on for the page appears only when the email came from it, and then under its public name or a generic label.
A link that names no segment was issued before segment opt-outs existed. It arrives with All emails ticked, so it still does what it did.
Links are stateless: the ?t= token is a signature over the organization, the address, an issue time and the segment. Nothing is stored per link, so a token in already-delivered mail keeps working and there is no table that grows with your send volume.
GET renders the page and POST saves it — split so that a link scanner or a prefetching mail client cannot unsubscribe someone who merely received the email. The saved form is the desired state, so a recipient can also rejoin a segment they left. Only the segments the page offered are changed.
Managing the list
curl http://api.localhost/v1/unsubscribes -H "X-API-Key: $KEY"
curl "http://api.localhost/v1/unsubscribes?segment_id=global" -H "X-API-Key: $KEY"
curl -X DELETE http://api.localhost/v1/unsubscribes/$ID -H "X-API-Key: $KEY"Removing an entry emits contact.resubscribed. Opting out emits contact.unsubscribed. Both carry segment_id, which is null for the scope "all email", and both emit only on a real state change, so a repeated call is a silent no-op rather than a storm of duplicate webhooks.