What RFC 8058 requires
RFC 8058 lets a mail app unsubscribe someone with one HTTPS POST, without opening a web page. For a message to qualify, check all of these:
The mail app then sends the List-Unsubscribe-Post value as the body of a POST to your URL, only after the user asks to unsubscribe. The body may arrive as multipart/form-data or application/x-www-form-urlencoded, so let the token in the URL do the work rather than depending on the body.
POST /unsubscribe/7f3a9c2e5b HTTP/1.1
Host: acme.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 26
List-Unsubscribe=One-ClicktinyRelay's rules for these headers
From the Send Email API reference:
Both headers are required. List-Unsubscribe alone does not enable one-click. Supplying one without the other advertises a capability the other does not back.
"headers": {
"List-Unsubscribe": "<https://acme.com/unsubscribe/7f3a9c2e5b>",
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click"
}Names are case-sensitive. Header names are matched exactly: send List-Unsubscribe, not list-unsubscribe.
// matched
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click"
// not matched
"list-unsubscribe-post": "List-Unsubscribe=One-Click"The URL must accept a POST and is written in angle brackets.
"List-Unsubscribe": "<https://acme.com/unsubscribe/7f3a9c2e5b>"Your header replaces the default. Supplying List-Unsubscribe overrides the default unsubscribe header tinyRelay generates, and tinyRelay adds no unsubscribe link of its own to your HTML.
// no List-Unsubscribe in headers: tinyRelay generates its default header
"headers": {}Add one-click unsubscribe, step by step
1. Create an unsubscribe URL that accepts POST
Generate an opaque token per recipient and list, and serve POST /unsubscribe/<token>. Answer with a status code, never a redirect.
// Mail apps POST here when the user clicks Unsubscribe.
// The token identifies the recipient and the list; nothing else is needed.
app.post("/unsubscribe/:token", async (req, res) => {
const done = await unsubscribeByToken(req.params.token); // your own lookup
res.sendStatus(done ? 200 : 404); // answer directly: never redirect a one-click POST
});# Mail apps POST here when the user clicks Unsubscribe.
# The token identifies the recipient and the list; nothing else is needed.
@app.post("/unsubscribe/<token>")
def one_click_unsubscribe(token):
if not unsubscribe_by_token(token): # your own lookup
return "", 404
return "", 200 # answer directly: never redirect a one-click POST2. Add both headers to the request
Put them in the top-level headers object. Headers apply to the whole request, so when each recipient needs their own URL, send one request per recipient.
{
"subject": "Your weekly Acme activity summary",
"body": "<p>Hi [FIRST_NAME], here is what happened in your workspace this week.</p>",
"sender": { "from": { "name": "Acme", "email": "updates@acme.com" } },
"recipients": {
"to": [
{ "email": "ann@example.com", "attributes": { "FIRST_NAME": "Ann" } }
]
},
"headers": {
"List-Unsubscribe": "<https://acme.com/unsubscribe/7f3a9c2e5b>",
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click"
},
"metadata": { "user_id": "u_1042", "list": "weekly_summary" }
}// Build one request per recipient so each gets their own unsubscribe URL
const token = await createUnsubscribeToken(user.id, "weekly_summary"); // your own opaque token
await sendEmail({
subject: "Your weekly Acme activity summary",
body: "<p>Hi [FIRST_NAME], here is what happened in your workspace this week.</p>",
sender: { from: { name: "Acme", email: "updates@acme.com" } },
recipients: { to: [{ email: user.email, attributes: { FIRST_NAME: user.firstName } }] },
headers: {
"List-Unsubscribe": `<https://acme.com/unsubscribe/${token}>`,
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
},
metadata: { user_id: user.id, list: "weekly_summary" },
});# One request per recipient so each gets their own unsubscribe URL
token = create_unsubscribe_token(user.id, "weekly_summary") # your own opaque token
send_email({
"subject": "Your weekly Acme activity summary",
"body": "<p>Hi [FIRST_NAME], here is what happened in your workspace this week.</p>",
"sender": {"from": {"name": "Acme", "email": "updates@acme.com"}},
"recipients": {"to": [{"email": user.email, "attributes": {"FIRST_NAME": user.first_name}}]},
"headers": {
"List-Unsubscribe": f"<https://acme.com/unsubscribe/{token}>",
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
},
"metadata": {"user_id": user.id, "list": "weekly_summary"},
})Base URL from current site samples; confirm in the docs.
sendEmail and send_email are the helpers from send email with Node.js and send email with Python.
3. Sign your sending domain with DKIM
tinyRelay signs your mail once you add the CNAME tec1._domainkey.yourdomain.com pointing to tec1.dkim.tinyemail.com. To confirm one-click is eligible, open a delivered message's source and check that the DKIM-Signature h= list includes both headers. Read transactional email deliverability for the DNS steps.
4. Record the opt-out
When you supply your own URL, the one-click POST goes to your server, so your system is where the opt-out is recorded. Stop sending that list to the recipient straight away. tinyRelay also has an UNSUBSCRIBE webhook event and bounce category 90 (Unsubscribe); see email webhooks.
Troubleshooting
Both headers present, names in exact case, URL in angle brackets and HTTPS, and the DKIM h= list covering both headers.
Your endpoint must accept POST and answer directly. A redirect or a GET-only route breaks one-click.
The token must identify the recipient. Headers apply to the whole request, so use one request per recipient when URLs differ.
The name is probably lowercase. Header names are matched exactly.
FAQ
A standard that lets a mail app unsubscribe someone with a single HTTPS POST. The message carries a List-Unsubscribe header with an HTTPS URL and a List-Unsubscribe-Post header set to List-Unsubscribe=One-Click.
No. The tinyRelay docs say List-Unsubscribe alone does not enable one-click. Send List-Unsubscribe-Post with the value List-Unsubscribe=One-Click as well.
Yes. Header names in the request body are matched exactly. Send List-Unsubscribe and List-Unsubscribe-Post, not lowercase versions.
Accept a POST and unsubscribe the recipient it identifies, without a redirect. RFC 8058 also requires the URL to carry enough information to identify the recipient and the list.
tinyRelay generates its default unsubscribe header. Supplying your own List-Unsubscribe overrides that default, and tinyRelay then adds no unsubscribe link of its own to your HTML.
Next steps
- Gmail and Yahoo sender requirements
- Transactional vs marketing email
- Transactional email deliverability
- Account notification emails
- Send Email API reference (docs)