API Reference
API Reference
GitHub
Tracking and Engagement

Unsubscribe & One-Click

Hosted unsubscribe pages, RFC 8058 one-click unsubscribe, and List-Unsubscribe headers

Every message Mailyard sends can carry a List-Unsubscribe header. There are two ways to put one there, and which you want depends on who owns the opt-out:

  • Mailyard-managed — you reference an unsubscribe list, Mailyard mints a signed one-click URL, hosts the page, and records the opt-out on its own suppression list.
  • Caller-managed — your application supplies its own URL, Mailyard carries the header and nothing else. Suitable when the application already runs its own preference centre and knows who its recipients are.

Campaigns are always Mailyard-managed.

Why it matters even for a non-marketing send

Gmail and Yahoo require a working List-Unsubscribe on bulk mail. Neither bounces a message that lacks one, they filter it, so the sender sees a clean delivery report and the recipient sees nothing. An application relaying a hundred thousand notifications is bulk mail by their definition regardless of what it is about.

Mailyard-managed opt-out

Hosted unsubscribe endpoints

GET  /tracking/unsubscribe/{token}
POST /tracking/unsubscribe/{token}

Both campaign and transactional opt-outs land here. They are public — no authentication: the URL carries an HMAC-signed token instead of an Authorization header, because the caller is a recipient or their mailbox provider.

  • GET renders a small confirmation page showing the address and a confirm button.
  • POST performs the opt-out. This is also the RFC 8058 one-click target, so it is idempotent and safe for a mailbox provider to call unattended.

The token encodes what is being unsubscribed, and the two kinds cannot be swapped for each other:

KindPayloadEffect of a confirmed opt-out
Campaignthe campaign message idSuppresses the subscriber on that campaign’s list and marks the message unsubscribed
Transactionalthe unsubscribe list id and the recipient addressSuppresses that one address, scoped to that one list

Neither token expires. An unsubscribe link in a message already delivered has to keep working.

An opt-out fires no webhook. It writes a suppression, and the way to read opt-outs from outside is GET /api/v1/suppressions - each row carries its unsubscribe_list_id, so a scoped opt-out is distinguishable from a global block. There is no email.unsubscribed event to subscribe to; see Event Types for the seven that do exist.

Scoping a send to a list

{
    "from": "news@example.com",
    "to": [
        "jane@example.com"
    ],
    "subject": "Weekly digest",
    "html": "<p>...</p>",
    "unsubscribe_list_id": "0f7c1b2e-2f1a-4a5e-9c3d-7b1f0a2d5e64"
}

Mailyard then mints the signed URL, emits both headers with one-click enabled, and resolves {{ mailyard_unsubscribe_url }} in the body to the same link.

A scoped send is limited to one recipient. The token binds a single address, so there is no correct link to put in a message addressed to several people, and Mailyard refuses the send rather than mint one that would opt out the wrong person.

Recipients already opted out of that list are dropped from the send, in addition to the project’s global suppressions.

Caller-managed opt-out

Supply the targets directly and Mailyard emits them verbatim. What happens when the recipient acts on them is entirely your application’s business — Mailyard neither receives the click nor records anything.

{
    "from": "notifications@example.com",
    "to": [
        "jane@example.com"
    ],
    "subject": "Your weekly summary",
    "html": "<p>...</p>",
    "list_unsubscribe_url": "https://app.example.com/prefs/u/eyJ1IjoxN30",
    "list_unsubscribe_mailto": "unsubscribe@example.com",
    "list_unsubscribe_post": true
}
FieldNotes
list_unsubscribe_urlAn http or https endpoint. Emitted as the second URI.
list_unsubscribe_mailtoA mailto: URI. A bare address is accepted and completed. Emitted first.
list_unsubscribe_postAdds List-Unsubscribe-Post, which invites the provider to opt the recipient out with a single unattended POST to list_unsubscribe_url. Requires that field.

Available on POST /api/v1/emails/send, POST /api/v1/emails/send-template, and per item on POST /api/v1/emails/batch — per item because the link identifies the recipient, and one link shared across a batch would opt out nobody in particular.

Only set one-click if you mean it

list_unsubscribe_post is a promise that an unauthenticated POST to that URL unsubscribes the recipient. A provider will make that request without asking. If the endpoint answers with a login page or a 405, the message looks worse than it would have with no header at all.

Not combinable with a managed list

unsubscribe_list_id and list_unsubscribe_url on the same send are refused with a 400. They are opposite arrangements: the list means Mailyard filters the send and mints the link, so a header pointing somewhere else would leave recipients who used it still receiving the mail, because their opt-out never reached the list doing the filtering.

Over the SMTP relay

An application relaying through Mailyard composes an ordinary RFC 2369 header, and that is what the relay honors:

List-Unsubscribe: <mailto:unsubscribe@example.com>, <https://app.example.com/prefs/u/abc>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

Both headers are lifted off the submission and re-emitted by Mailyard’s own message builder, which is what keeps a single, validated copy on the wire. A URI in a scheme Mailyard cannot use is dropped rather than treated as an error.

The relay has no equivalent of unsubscribe_list_id. A submitted message is always caller-managed.

On the wire

Mailyard emits the mailto first, then the URL, matching the order most clients expect:

List-Unsubscribe: <mailto:unsubscribe@example.com>, <https://app.example.com/prefs/u/abc>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

List-Unsubscribe and List-Unsubscribe-Post are reserved in the headers map on a send. Setting them there is rejected with a 400 pointing at the fields above — the message builder owns these headers so there is exactly one of each, from one place that has validated it.

Relationship to the unsubscribe URL system variable

{{ mailyard_unsubscribe_url }} resolves to the hosted endpoint for the send type: the campaign unsubscribe page for campaigns, the one-click endpoint for a send scoped to an unsubscribe list. It is only substituted once the message identity is known, so it renders as its own name in template previews. It has no value on a caller-managed send, where Mailyard does not know the link. See System Variables .

Unsubscribe lists and suppression

Mailyard-managed unsubscribe lists are project-scoped resources you manage via the API:

POST   /api/v1/unsubscribe-lists
GET    /api/v1/unsubscribe-lists
GET    /api/v1/unsubscribe-lists/{id}
PATCH  /api/v1/unsubscribe-lists/{id}
DELETE /api/v1/unsubscribe-lists/{id}

A list has a name, an optional public_name and description, and an active flag. Referencing one by unsubscribe_list_id on a send scopes a one-click opt-out to it.

When a recipient unsubscribes, they land on your suppression list so future sends skip them. See Suppression List for managing suppressions and Contact Management for how suppression interacts with contacts.