Buying a component does not delete its work. It moves the work to the seam between the vendor’s model and what you assumed the vendor’s model was, and the seam is not covered by anybody’s SLA.

Unsubscribe shows this more clearly than anything else, because three separate defects tend to land in the same place, and in none of them is the email vendor at fault. The walkthrough below is illustrative: a team buys an email vendor, wires up its own unsubscribe route, and ships all three. You can find the first one on your own domain with a single command.

The unsubscribe control that answers 403

RFC 8058 one-click unsubscribe is a specific HTTP request. The mail client POSTs to the URL in your List-Unsubscribe header with the body List-Unsubscribe=One-Click, carrying no cookies, no authorization and no other context.[1] Gmail requires it of anyone sending more than 5,000 messages a day to Gmail accounts, alongside a clearly visible unsubscribe link in the body of the message.[3] Yahoo asks for the same header and wants the unsubscribe honoured within two days.[4] Below those volumes a mailbox provider has less reason to penalise a sender for getting this wrong. The reader who pressed the button is just as annoyed either way.

Now put that route behind a web framework with cross-site request protection switched on by default. Astro is a clean example. Its security.checkOrigin defaults to true, and it rejects a POST with a 403 when the origin header does not match the request.[2] A POST with no browser behind it has no origin header to match.

$ curl -s -o /dev/null -w '%{http_code}\n' -X POST \
    'https://example.com/api/unsubscribe?one-click=1' \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    --data 'List-Unsubscribe=One-Click&token=invalid'
403
Illustrative. A one-click POST against an unsubscribe route that sits behind a framework origin check

That 403 comes from the framework, not from the route. The request never reached the team’s code. A working route would have answered 400, because the token does not verify, and 400 is the answer it should have been giving all along.

The header in the outgoing mail is correct. It is what a mailbox provider reads when it decides whether to show its own Unsubscribe control next to the sender name. The URL behind that header answers 403.

Nothing about the failure shows up on the sending side. The vendor reports the message as delivered, because it was. The application logs show nothing, because the route never ran. The reader presses Unsubscribe, the mail client tells them whatever it tells them, and the only party in a position to notice is the mailbox provider scoring the sender.

It also tends to stay hidden, for a structural reason. Newsletter mail usually goes out through the vendor’s broadcast feature, and a broadcast carries the vendor’s own unsubscribe URL. So every unsubscribe anyone watches working goes through the vendor’s path. The route the team owns only appears on mail it sends directly, typically transactional mail such as a double opt-in confirmation. That route can be broken from the day it ships until the first message goes out without a broadcast behind it.

Run the command against your own domain before you read further. If your unsubscribe URL is served by a framework with CSRF protection on form-encoded POSTs (Astro, SvelteKit and Next server actions all ship something like this), the check takes ten seconds and the answer is better had now.

What that check was actually protecting

The one-line fix is to turn the check off. That deserves more thought than one line, because turning off a security default to make an email work is how teams end up explaining themselves later. So find out what the check was covering first.

checkOrigin only inspects requests whose content type is application/x-www-form-urlencoded, multipart/form-data or text/plain.[2] Everything else passes through untouched.

$ curl -s -o /dev/null -w '%{http_code}\n' -X POST \
    'https://example.com/api/unsubscribe?one-click=1' \
    -H 'Content-Type: application/json' --data '{}'
400
Illustrative. Same route, same method, one header different

Same URL, same method, and this time the request reaches the route. On a site whose other POST endpoints send JSON, which describes most sites built this decade, the default was protecting almost nothing except the unsubscribe route itself. And that route should already authenticate every request on its own. With a signed, per-recipient, expiring token in the URL, anyone able to forge the request already holds a valid token for the address they want to remove. The origin header is not what stops them.

That makes turning the check off defensible for this one route. Astro has no per-route opt-out, though, so the only switch available is global.[2]

The cost of the global switch: the day the site gains a form that POSTs form-encoded data and authenticates with a cookie, the protection that would have covered it is already off, and whoever adds that form will not know. A global setting is the wrong shape of fix for a problem in one route. If your site already has cookie-authenticated form posts, do not take this route. Move the unsubscribe endpoint out of the framework instead, for example into a small edge worker route in front of the app. That is about an hour of work.

The flag that unsubscribes them from everything

The second defect is a data-model mismatch rather than a framework default.

Many email vendors store contacts at the account level, not the list level. Resend is one documented example. Its unsubscribed field on a contact is the contact’s global subscription status, and setting it to true unsubscribes the contact from all broadcasts.[5] Not just the list they asked to leave. Everything sent from that account.

Reading “unsubscribe the contact” as “unsubscribe the contact from this list” is the obvious reading, and it is wrong. For any team running more than one list on a single vendor account, one unsubscribe becomes a silent removal from every list.

Membership in one list is whatever the vendor calls a segment or an audience. Leaving one list therefore means removing that one membership and nothing else. The route should do exactly that, never touch the global flag, and say so in a comment at the top of the file, where the next person to edit it will see it.

Every vendor with a contacts model draws a line somewhere between the person and the list, and the word “unsubscribe” sits on one side of that line. Find out which side before you write to the field. The API will accept the wrong one without complaint.

The GET that unsubscribes people who never clicked

The third defect is the cheapest of the three to get wrong.

Safe methods are the ones whose semantics are essentially read-only, and a user agent may perform them automatically for exactly that reason.[6] Corporate mail filters, link scanners and prefetchers use that permission: they fetch every URL in an email before a human ever sees it.

Put the unsubscribe action behind a GET and the reader’s own security appliance removes them from the list at 3am, without a click. The reader never knows. The sender sees the churn and blames the subject line.

The fix: the GET renders a page with a button, and the POST does the work. The one-click header is the sanctioned exception, and it is a POST by specification, which is the whole reason RFC 8058 specifies a POST.[1]

The confirmation link in a double opt-in is usually a GET that does act, and it is worth saying why the same argument does not apply there. The only thing a scanner can trigger on that link is subscribing the address the token was minted for, which is the address that asked. At worst, someone who requested a subscription gets one. The risk is not symmetric, so the design does not need to be either.

What this says about build versus buy

Build versus buy says to buy email and stop thinking about it. The first half of that is right. Deliverability is a reputation game played over years against mailbox providers who will not publish their rules, and a new sending IP has no reputation at all. Nothing here argues for sending your own mail.

The “stop thinking about it” half is too comfortable. What you buy from an email vendor is delivery. What you keep is consent: who is on the list, how they got there, and how they leave. All three defects live in that second half, and no vendor was ever going to catch them, because from the vendor’s side each one looks like a successful API call.

Buying gets argued as if it hands over the whole component. It hands over the middle (the SMTP conversation, the IP warming, the feedback loops) and leaves you both ends: the model you integrate against, and the obligations you carry regardless of who sends the bytes.

Budget for the ends. The next time you are deciding whether to buy something, ask: what can this vendor not afford to get wrong, and what does that leave me holding? For email, the vendor cannot afford to get delivery wrong, and you are holding consent. Write down your half before you integrate, because it will not appear in the onboarding guide.

Here are the three checks, in the order worth running them on any stack.

CheckWhat a wrong answer looks like
POST your one-click unsubscribe URL with Content-Type: application/x-www-form-urlencodedAny status your own route did not produce. A 403 from the framework means the request never reached your code.
Read what your vendor’s unsubscribe flag is scoped toThe words “global”, “account”, or “all broadcasts”. If the word is not “list”, writing to it removes the reader from everything you send.
Request the unsubscribe URL as a GETIt unsubscribes somebody. A mail scanner will find that before a human does.
Half an hour, and you own the answers rather than assuming them

Sources

  1. RFC 8058: Signaling One-Click Functionality for List Email Headers - IETF

    Supports: One-click unsubscribe is a POST whose body is the literal string List-Unsubscribe=One-Click, which MUST NOT include cookies, HTTP authorization or any other context information, and which must not be answered with a redirect.

  2. Configuration Reference - Astro , accessed

    Supports: Astro's security.checkOrigin defaults to true, applies only to POST, PATCH, DELETE and PUT requests carrying application/x-www-form-urlencoded, multipart/form-data or text/plain, and answers a mismatch with a 403.

  3. Email sender guidelines - Google , accessed

    Supports: Senders of more than 5,000 messages a day to Gmail accounts must support one-click unsubscribe and also include a clearly visible unsubscribe link in the message body.

  4. Sender Best Practices - Yahoo Inc. , accessed

    Supports: Yahoo asks senders to implement a functioning list-unsubscribe header supporting one-click unsubscribe, and to honor unsubscribes within 2 days.

  5. Create Contact - Resend , accessed

    Supports: Resend's unsubscribed field is documented as the contact's global subscription status: set it to true and the contact is unsubscribed from all broadcasts.

  6. RFC 9110: HTTP Semantics - IETF

    Supports: Safe methods are those whose semantics are essentially read-only, and a user agent may perform them automatically, which is why an action must never live behind a GET.