All articles

How to Implement One-Click Unsubscribe (RFC 8058) in Amazon SES, and Why Gmail, Yahoo and Outlook Now Demand It

1st July, 2026

How to Implement One-Click Unsubscribe (RFC 8058) in Amazon SES, and Why Gmail, Yahoo and Outlook Now Demand It

Your Footer Unsubscribe Link Is No Longer Enough

For years, dropping an unsubscribe link at the bottom of a marketing email was considered good practice. It satisfied CAN-SPAM, kept regulators quiet, and gave recipients a theoretical exit. Today that approach is insufficient in a concrete, deliverability-breaking sense. Gmail, Yahoo and Microsoft Outlook now require bulk senders to implement machine-readable, one-click unsubscribe headers, and all three providers have moved from gentle guidance to active enforcement. If you send marketing email through Amazon SES and you have not yet added the correct headers, you are already at risk of rejections, complaint spikes and account review.

This guide explains what one-click unsubscribe means at the protocol level, which provider mandates apply to you, and how to implement the two required headers correctly in Amazon SES, whether you use the API, raw SMTP or email templates. It also covers how to handle the server-side callback, and how a broken unsubscribe flow appears first as a rising complaint rate in SES, before any formal enforcement action arrives.

What One-Click Unsubscribe Actually Means: RFC 8058 in Plain Terms

One-click unsubscribe is a standard defined in RFC 8058, published in January 2018. It operates entirely through email headers, not through the body of the message. When a mailbox provider such as Gmail detects the correct headers in a message, it renders its own Unsubscribe button near the sender name at the top of the email interface. When the recipient clicks that button, the mail client fires an HTTP POST request directly to a URL you have specified. The recipient is unsubscribed without visiting any web page, completing any form, or clicking any confirmation link.

This is fundamentally different from the traditional footer link, which takes recipients to a landing page, sometimes asks them to confirm their address, and occasionally redirects through multiple screens before processing the request. RFC 8058 defines the two headers you must include: List-Unsubscribe, which carries the HTTPS URL for the POST endpoint, and List-Unsubscribe-Post, which signals to the mail client that the mechanism supports a one-click POST action. Both headers must be present. The List-Unsubscribe-Post header always contains the fixed value List-Unsubscribe=One-Click. Without it, Gmail and Yahoo treat the header set as incomplete and will not surface the native unsubscribe button.

It is also worth being precise about what the standard does not require. You do not need to replace your footer link. You need to add the headers alongside it. The footer link serves human recipients who prefer to manage their subscriptions manually. The headers serve mail clients and enable the automated, machine-readable mechanism that the major providers now mandate.

The Enforcement Timeline: Gmail, Yahoo and Microsoft Outlook

Google and Yahoo announced their bulk sender requirements together in October 2023, with enforcement beginning in February 2024. The one-click unsubscribe requirement applied specifically to senders of 5,000 or more messages per day to personal Gmail and Yahoo accounts, with a hard implementation deadline of 1 June 2024. Gmail's initial approach was graduated: non-compliant traffic received temporary 4xx error codes, and messages were more likely to be filtered to spam rather than rejected outright.

That changed in November 2025. Gmail moved from soft enforcement to full enforcement, and non-compliant messages now face temporary rate limiting or permanent 5xx rejections. Messages are no longer simply hidden in spam folders; they are blocked before reaching the inbox. Google's spam rate thresholds operate in two tiers: the target is below 0.10%, and the hard enforcement threshold is 0.30%. Exceeding 0.30% triggers active filtering and removes access to Google's delivery mitigation support channels.

Microsoft followed with its own bulk sender requirements, announced on 2 April 2025, with enforcement beginning on 5 May 2025. Non-compliant emails to Outlook.com, Hotmail.com and Live.com addresses from senders exceeding 5,000 messages per day are routed to the Junk folder, with a further phase of outright rejection planned at a date to be announced by Microsoft. Microsoft's error code for non-compliant bulk mail is 550 5.7.515. Microsoft's official guidance refers to "functional unsubscribe links" rather than explicitly citing RFC 8058 by name, but multiple sources confirm that the RFC 8058 header pair satisfies the requirement and that organisations sending to all three providers have no practical reason to skip the full implementation.

Yahoo has enforced its requirements in parallel with Google since February 2024, requiring the same List-Unsubscribe and List-Unsubscribe-Post headers and the same 48-hour processing window for unsubscribe requests.

Which Amazon SES Emails Are in Scope

The one-click unsubscribe requirement applies to marketing and promotional messages, not to transactional email. Transactional messages, such as password resets, order confirmations, shipping notifications and account alerts, are exempt from the requirement across all three providers. If you are sending both categories from the same domain in SES, only the marketing messages strictly need the headers. That said, adding the headers to transactional messages does no harm, and it simplifies implementation if you manage a single sending pipeline.

The scope threshold of 5,000 messages per day is cumulative across your sending domain. Marketing sends, transactional sends and automated notifications all count toward that figure. If you are approaching but not yet at that volume, implementing RFC 8058 now protects you from an enforcement surprise when you cross the threshold. Amazon Web Services itself recommends that SES senders operating outside the sandbox assume the bulk sender requirements apply to them.

One classification trap to watch for: a transactional receipt or order confirmation that includes a promotional upsell or cross-sell block may be categorised as a marketing message by a mailbox provider's filtering systems, regardless of your intent. If your transactional templates carry promotional content, treat them as marketing messages for the purposes of these headers.

The Two Required Headers: Correct Syntax

The List-Unsubscribe header must contain an HTTPS URL pointing to your POST endpoint. While RFC 2369 permits a mailto: value in this header, a mailto: address alone does not satisfy the one-click requirement under RFC 8058. You may include both a URL and a mailto address in the same header, which improves compatibility with older mail clients that do not support HTTP POST. The URL must be unique per recipient so that your endpoint knows which subscriber to suppress.

The correct header pair looks like this:

List-Unsubscribe: <https://unsubscribe.example.com/unsub?h=HMAC_HASH>, <mailto:unsub@example.com?subject=unsubscribe>

List-Unsubscribe-Post: List-Unsubscribe=One-Click

The URL should contain a hashed or otherwise opaque identifier that encodes the recipient address and any list or topic identifiers. RFC 8058 recommends this explicitly: the URI should contain a hard-to-forge component to prevent unauthorised unsubscribe requests. A common approach is an HMAC-SHA256 of the recipient email address combined with a server-side secret key. This value is generated at send time, embedded in the URL, and verified at request time by your endpoint.

Implementation Walkthrough 1: Adding Headers via the Amazon SES API

When using the SES v2 SendEmail API operation, you add custom headers through the Headers field available in the Simple and Templated content types. The structure is straightforward: you pass an array of header objects, each with a Name and a Value key.

In Python using boto3, the relevant section of your send_email call looks like this:

Content={ 'Simple': { 'Headers': [ { 'Name': 'List-Unsubscribe', 'Value': '<https://unsubscribe.example.com/unsub?h=' + recipient_hash + '>, <mailto:unsub@example.com?subject=unsubscribe>' }, { 'Name': 'List-Unsubscribe-Post', 'Value': 'List-Unsubscribe=One-Click' } ], ... } }

The recipient_hash variable must be computed before the API call, uniquely per recipient. Compute it by applying HMAC-SHA256 to the recipient address using a stable secret held in your application. Store the mapping between the hash and the recipient address in your database so the endpoint can resolve it at unsubscribe time.

For bulk sends using SendBulkEmail, the same Headers field is available, but because the hash must be unique per recipient, you will need to either use a per-message destination override or generate individual hashes during template personalisation and inject them as template variables.

Implementation Walkthrough 2: Adding Headers via SMTP

If you connect to SES through its SMTP endpoint rather than the API, you add the headers in the raw message payload. In most sending libraries and frameworks, this means inserting them into the email object before it is serialised and transmitted. In Python's email.mime library, you attach them directly to the message object:

msg['List-Unsubscribe'] = '<https://unsubscribe.example.com/unsub?h=' + recipient_hash + '>'

msg['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click'

In PHPMailer, use the addCustomHeader method. In Node.js with Nodemailer, use the headers property in the mail options object. The SES SMTP endpoint transmits whatever headers are present in the raw message, so any SMTP library that allows custom header injection will work. Verify your implementation by sending a test message to a Gmail address and inspecting the raw headers via the Show original option in the Gmail interface, which will confirm whether both headers are present and correctly formed.

Implementation Walkthrough 3: Embedding Headers in SES Email Templates

SES email templates, created via the CreateEmailTemplate API, support personalisation variables using double-brace syntax such as {{variable_name}}. You can use this to inject a per-recipient hash into the List-Unsubscribe header value at send time. When calling SendBulkEmail with a template, pass each recipient's unique hash as a template data variable and reference it as part of the URL string in the header definition of the send call.

SES's native subscription management feature, enabled via the ListManagementOptions parameter or the X-SES-LIST-MANAGEMENT-OPTIONS SMTP header, provides a managed alternative. This feature inserts an amazonSESUnsubscribeUrl placeholder into your templates and handles some of the header generation automatically. However, this built-in mechanism is tied to SES's own subscription management flow. If you manage your subscriber list in an external system, such as a CRM, a custom database or a marketing platform, you will need to implement your own headers and endpoint rather than relying solely on the managed option.

Building the Server-Side POST Endpoint

When a recipient clicks the unsubscribe button in Gmail, Yahoo or another supporting mail client, your server receives an HTTP POST request to the URL embedded in the List-Unsubscribe header. The request body contains the parameter List-Unsubscribe=One-Click, posted with a Content-Type of application/x-www-form-urlencoded. Your endpoint must handle this correctly.

The endpoint should: verify the hash parameter in the URL to confirm the request is legitimate; look up the recipient address associated with that hash; immediately add the address to your suppression list; and return an HTTP 200 response. Do not redirect. Do not require authentication from the requesting party. Do not return a 4xx or 5xx status for valid requests. Mail clients that receive repeated errors from your endpoint may treat your headers as non-functional, which undermines your compliance standing.

A suitable minimal implementation in a serverless architecture uses Amazon API Gateway exposing a Lambda function. The Lambda reads the hash from the query string, validates it against your stored HMAC, resolves the recipient address, and calls both your own database to mark the subscriber as unsubscribed and the SES v2 API to add the address to your account-level suppression list. The suppression call uses the PutSuppressedDestination operation with a reason of COMPLAINT.

Wiring the Unsubscribe into Your SES Suppression List

Amazon SES maintains an account-level suppression list that prevents sending to addresses that have previously generated bounces or complaints. For SES accounts created after November 2019, this list is enabled by default for both bounces and complaints. For older accounts, it must be enabled explicitly using the PutAccountSuppressionAttributes API call or the equivalent CLI command.

When your unsubscribe endpoint processes a POST request, add the recipient address to this suppression list alongside your own database using the PutSuppressedDestination operation in the SES v2 API. This ensures that even if there is a bug in your application-level suppression logic, SES itself will not attempt delivery to that address. Addresses on the account-level suppression list remain there until explicitly removed, and SES does not count messages to suppressed addresses towards the Reputation.ComplaintRate metric, which matters directly for your sending health scores.

The 48-hour processing window mandated by Gmail and Yahoo is a ceiling, not a target. Best practice is to suppress immediately and synchronously within the POST request handler. Batch processing or overnight jobs do not satisfy this requirement and introduce the risk of sending one more campaign to a recipient who has already unsubscribed.

Common Mistakes That Break Compliance

Several implementation errors recur often enough to warrant direct attention. Providing only a mailto: value in the List-Unsubscribe header without an HTTPS URL does not satisfy RFC 8058. Gmail and Yahoo require the HTTPS POST mechanism. The mailto option is a useful fallback for older clients but cannot replace the URL-based mechanism.

Including the List-Unsubscribe header without the List-Unsubscribe-Post header means the mail client will not treat the mechanism as one-click compliant. Both headers must appear together in the same message.

An endpoint that returns 4xx or 5xx errors for valid POST requests is functionally equivalent to having no endpoint at all. A header pointing to a broken endpoint is considered worse than no header, because mail clients may flag your sending domain for repeated failures.

Using a static URL that does not uniquely identify the recipient means your endpoint cannot determine who to unsubscribe. Every URL in the List-Unsubscribe header must encode a per-recipient identifier.

Processing unsubscribe requests in a manual or weekly batch job violates the 48-hour requirement. Requests must be handled promptly. In practice, immediate synchronous processing is the only safe approach.

How a Broken Unsubscribe Flow Shows Up in Your SES Complaint Rate

When a recipient cannot easily unsubscribe, the most convenient remaining option is the Report Spam or Mark as Junk button. This generates a complaint that is reported back to SES via feedback loops from mailbox providers. A missing or broken one-click unsubscribe mechanism therefore does not fail silently: it fails measurably as an upward trend in your SES complaint rate.

Amazon SES tracks complaint rate as a reputation metric and enforces its own thresholds independently of what Gmail or Yahoo do. SES considers a complaint rate below 0.10% healthy. Once your rate exceeds that level, your account moves to Under Review status, which may lead to your sending being paused. These thresholds sit on top of Gmail's own enforcement, meaning a single broken unsubscribe flow can trigger consequences from both AWS and the receiving mailbox provider at the same time.

The relationship between unsubscribe friction and complaint rate is direct. When unsubscribing is easier than reporting spam, most disengaged recipients choose the former. When the one-click header is missing and the Gmail Unsubscribe button does not appear, a meaningful proportion of recipients will reach for the spam button instead. The complaint rate is therefore the earliest quantitative signal that your unsubscribe implementation has a problem.

Using SES Monitor to Catch Complaint Rate Spikes Before AWS Acts

Monitoring your SES complaint rate reactively through the AWS console is insufficient for fast-moving bulk campaigns. By the time a rate spike appears prominently in the console, or triggers an AWS enforcement notice, it may already have been accumulating for days across thousands of messages. The practical need is for near-real-time visibility and alerting at meaningful thresholds, not only at the point where AWS begins its review process.

SES Monitor watches your SES complaint rate, bounce rate and sending reputation metrics continuously, alerting you when rates begin to climb rather than after they have already breached AWS thresholds. If you push a campaign with a malformed List-Unsubscribe header, or if a new deployment inadvertently strips the headers from outgoing messages, the resulting complaint signal will appear in SES Monitor within the sending window. That gives you time to halt the campaign, fix the headers and suppress the affected audience before AWS opens a review case.

A rising complaint rate on a campaign that did not previously generate high complaints is almost always a signal that something in your sending path has changed: a header is missing, an endpoint is returning errors, or a suppression list is not being populated. Catching that signal early, before it compounds across multiple sends, is exactly the kind of operational visibility that separates teams with healthy sending reputations from those that discover problems through enforcement notifications.

Compliance Checklist Before Your Next Bulk Send

Before sending your next marketing campaign through Amazon SES, verify the following points. Both the List-Unsubscribe and List-Unsubscribe-Post headers are present in every marketing message. The List-Unsubscribe header contains a valid HTTPS URL with a unique, HMAC-hashed per-recipient identifier. The List-Unsubscribe-Post header contains exactly the value List-Unsubscribe=One-Click. Your POST endpoint returns HTTP 200 for valid requests and does not require authentication from the requesting mail client. Your endpoint suppresses the recipient immediately in both your application database and the SES account-level suppression list via PutSuppressedDestination. You have verified the headers are present by sending a test message and inspecting raw headers in Gmail. Your SES account-level suppression list is enabled for both bounces and complaints. You have complaint rate monitoring and alerting in place, with thresholds set well below the AWS review trigger of 0.10%. Transactional messages that contain promotional content have been reviewed and treated as marketing messages for header purposes. A footer unsubscribe link remains present in the message body alongside the headers, as both Gmail and Yahoo require a visible body link in addition to the machine-readable headers.

RFC 8058 compliance is not a large engineering task. The headers are two lines of configuration per message. The endpoint is a small, stateless function. The suppression list call is a single API operation. The cost of not implementing it, measured in blocked messages, complaint-rate enforcement actions and AWS account reviews, is orders of magnitude larger than the cost of doing it correctly.

Start protecting your SES reputation today

Connect your AWS SES account in a couple of minutes and get real-time bounce and complaint alerts before a problem becomes a suspension.

No credit card required · 2-minute setup · Cancel anytime