BlogDeliverability

Unsubscribe is two writes

Someone opts out on day one of a welcome sequence and still gets days 3, 6 and 10. The suppression worked. What it missed was the mail already queued, and the sequence that keeps queuing more. What an unsubscribe has to write, and why it is more than a flag.

Daniel Sternlicht6 min read

Someone signs up, gets the first email of a welcome sequence, and clicks unsubscribe. The page says they are unsubscribed. Two days later the second email arrives. Then the third. From where they sit, the unsubscribe link does not work, and the next thing they press is "Report spam".

Nothing in that story is a bug in the unsubscribe. The address was suppressed correctly. The problem is what an unsubscribe has to touch, and it is more than one row.

The one write everyone does

The obvious implementation of an unsubscribe is a flag: record that this address opted out, and check the flag before sending. We do that. It is called a suppression, it is checked in send() for every recipient in to, cc and bcc, and it stops every future send.

The word doing the work there is future. A suppression is consulted when a message is sent. It says nothing about mail that was already written and is waiting for its time.

The mail that is already on its way

A welcome sequence is not sent; it is scheduled. Day 3, day 6 and day 10 can all be sitting in a queue as messages with a send time. An API caller can schedule a reminder for next Tuesday. Those messages were created while the person was still subscribed.

So the second write is to cancel them. cancelPendingForRecipient finds every scheduled message in the workspace that names the address, in to, cc or bcc, and does one of two things:

  • If someone else is still in to, it removes the address and leaves the message for them.
  • If nobody is left in to, it cancels the message, because it is no longer the one its sender wrote.

Every one of those writes is conditional on the message still being scheduled. That is the same lock the scheduler takes when it picks a message up, so a cancel and a send racing each other cannot both win, and a message already on its way is never rewritten underneath the sender.

The place this usually goes wrong is the lookup. A queue row names one recipient, the first to address, and matching on that alone misses anyone who was second in to, or in cc or bcc. A suppression check at send time still drops them, so nothing wrong is delivered. What breaks is the part everyone can see: the log and the dashboard go on saying the mail is coming until the moment it does not, and the person who unsubscribed has no way to know they are safe.

The sequence that keeps writing more

Cancelling the queue is not the end of it either, and this is the part the title undersells. An automation does not schedule all its steps up front. It sends step one, then works out step two when step two is due. Cancel the queued email and leave the enrolment active, and the sequence simply generates the next one.

To the person, that reads exactly like the first failure: an unsubscribe that did not work. So an unsubscribe ends every active enrolment for the address too, with the reason recorded as unsubscribed. Strictly, then, it is three writes: suppress, cancel what is queued, end the enrolments. The rule is still called "two writes" in our notes, because the second one is the one people forget and the third is the second one again, one level up.

Unsubscribing is not the only way out of a sequence. An enrolment has five automatic exits:

  1. Unsubscribing.
  2. Replying, when the automation is set to exit on reply. A drip that keeps arriving after someone has answered is the fastest way for a sequence to look broken, and a platform that receives mail can see the reply. Out-of-office and bounce messages do not count.
  3. Being suppressed, by a hard bounce or a complaint.
  4. Opting out of the automation's topic.
  5. A tag rule: gaining a tag that ends the sequence, or losing one it requires.

The eager cancellations are what make the dashboard right immediately. They are not the guarantee, because not every path that changes a preference passes through them: a contact import carrying a previous provider's opt-outs, the preference centre, a tag set from a CSV. The guarantee is one function, exitFor, asked at enrolment and asked again before every step. It reads the suppression, the topic and the tags, which are the three things a background path can change without anyone pressing anything. If the answer is that this person must not receive this step, the enrolment ends there, whatever path put them in that state. A reply and a one-click unsubscribe need no such sweep: they are events, and they end the enrolment as they arrive.

Not everything should stop

An unsubscribe that stops too much is also a bug, just a quieter one.

A marketing opt-out does not block transactional mail. Suppressions carry a scope. Unsubscribing writes one scoped to marketing, so the person's password reset and their receipt still arrive. Only hard bounces and complaints are suppressed with scope all: an address that does not exist cannot receive a receipt either, and someone who pressed spam has told the mailbox provider what they think of every message from you. This is the same line the platform draws between the mail. and news. sending identities, and one plan with two reputations only works if the suppressions respect it too.

A topic opt-out does not unsubscribe someone from everything. A person who wants fewer newsletters and is given only all-or-nothing often presses spam instead, and a complaint costs the sending domain far more than an opt-out. So when a message was sent under a topic, the footer link opts the person out of that topic, shows them the rest, and writes no suppression at all. A suppression row would silently block the topics they kept. Opting out of a topic still ends any sequence sent under it.

Preferences live on the person, not on a contact row: one record per workspace, address and topic, and no record means the topic's default applies. We deliberately do not write a row for everyone who never chose. If we did, changing a topic's default later would move nobody.

A transient bounce is not an unsubscribe. A full mailbox or a greylisting delay resolves on its own. Only permanent bounces suppress, because suppressing the transient ones silently shrinks a healthy list.

One click means one click

Gmail and Yahoo require one-click unsubscribe for bulk senders, and the mechanism is two headers: List-Unsubscribe and List-Unsubscribe-Post: List-Unsubscribe=One-Click. Every marketing message carries both, and the footer carries the same link, built for that recipient. When the message was sent under a topic, that link is the topic's, so even the provider's own button narrows the opt-out rather than ending everything. That is why a marketing message has exactly one recipient: a footer can only carry one person's opt-out.

The endpoint answers both methods. The mailbox provider sends a POST with that exact body, and it opts the person out on the spot. A person clicking the footer link sends a GET. Supporting only one of the two is a common way to keep collecting complaints from people who believe they already opted out.

The link is signed with a token scoped to both the workspace and the address, so a link minted by one sender cannot opt someone out of another sender's mail on a multi-tenant platform.

The checklist

If you run your own email, or you are evaluating someone else's, an unsubscribe has to do all of this:

  • Suppress the address for the kind of mail they opted out of, and no more.
  • Cancel what is already scheduled to them, including where they are not the first recipient.
  • End every sequence they are on, and re-check before each step rather than trusting that the cancellation caught everything.
  • Leave transactional mail alone.
  • Accept the one-click POST from the mailbox provider as well as the click from the person.

Most of the pain of email deliverability is slow: reputation built over weeks, as the warm-up post describes. This part is not. The complaint from someone who unsubscribed and kept receiving mail arrives in days, and each one is counted against the domain.

  • deliverability
  • unsubscribe
  • automations

Daniel Sternlicht

Co-founder, SendRaven

Try it

Give your agent an inbox, an outbox, and a seatbelt.

3,000 emails a month free, and an MCP server your agent can use today.

Start freeRead the docs