Skip to content

Unsubscribes ​

Suppression is a first-class list, not a flag on a contact. Every opt-out has a scope:

ScopeBlocks
All emailEvery send path below.
One segmentOnly 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/send takes 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 ​

PathBehaviour
POST /v1/sendRecipients 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_emailThe step is recorded as skipped for an opt-out from all email. Segment opt-outs do not apply.
BroadcastsAddresses 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 ​

bash
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.

Released under the Apache 2.0 License.