How to Make an Unsubscribe Link That Keeps You Out of Spam

A working unsubscribe link is now a hard requirement at Gmail and Yahoo, not a nicety. Here is how to build one — HTML, List-Unsubscribe headers, one-click, and the mistakes that get senders blocked.

Sep 5, 2026 10 min read 2,279 words
How to Make an Unsubscribe Link That Keeps You Out of Spam

TL;DR

  • An unsubscribe link is two things, not one: a visible HTML link in the body and a List-Unsubscribe header in the message. Gmail and Yahoo bulk-sender rules require both for anyone sending over 5,000 messages a day to their users.
  • One-click unsubscribe (List-Unsubscribe-Post: List-Unsubscribe=One-Click, RFC 8058) must process the opt-out in under two days, with no login, no survey, no confirmation page.
  • Cold outbound to individually researched B2B contacts is legally different from bulk marketing — but you still need a plain-text opt-out line, and you still benefit from honoring it instantly.
  • The fastest path to a compliant link: use your ESP's merge tag, verify the header renders, then test the flow end-to-end from a real inbox.
  • Most "unsubscribe problems" are actually list-quality problems. Bad addresses drive complaints, complaints drive filtering. Verify before you send.

An unsubscribe link is a personalized URL that identifies one recipient on one list and, when visited, removes them from future sends. Think of it as a hotel key card rather than a master key: it works for exactly one guest, one room, and it can be revoked.

Three components have to line up:

  1. A unique token per recipient. Never example.com/unsubscribe?email=jane@acme.com in plain text — that is enumerable and leaks addresses. Use a signed, opaque token (HMAC of subscriber ID + list ID + secret).
  2. A visible HTML link in the footer, in readable contrast, not 6px grey-on-grey.
  3. Mail headers — List-Unsubscribe and, for bulk senders, List-Unsubscribe-Post — so the mailbox provider can render its own native "Unsubscribe" button next to your sender name.

That third piece is the one most people skip, and it is the one Gmail actually checks. When Gmail shows its own unsubscribe control, recipients use it instead of hitting "Report spam." That single substitution is worth more to your sender reputation than any copy tweak you will make this quarter.

Marketer choosing one-click unsubscribe over reply STOP
Marketer choosing one-click unsubscribe over reply STOP

Start with the body link, because it is the part every recipient can see and every regulator expects.

<p style="font-size:12px;color:#555;text-align:center;">
  You are receiving this because you signed up at example.com.
  <a href="https://example.com/u/8f2a1c9e4b" style="color:#555;text-decoration:underline;">
    Unsubscribe
  </a>
  &middot; Example Inc., 100 Market St, San Francisco, CA 94103
</p>

Four rules for that block:

  1. Readable, not hidden. Minimum 12px, contrast ratio above 4.5:1 against the background. Hiding the link is the single most reliable way to convert an unsubscribe into a spam complaint.
  2. No image-only link. Images get blocked by default in many clients. If your unsubscribe lives inside a PNG, half your audience cannot use it.
  3. Include the postal address. CAN-SPAM requires a valid physical address; most ESPs will refuse to send without one.
  4. State why they are getting it. One sentence of provenance ("You downloaded our pricing guide in March") cuts complaint rates measurably.

If your ESP handles the list, replace the raw URL with its merge tag — {{unsubscribe}}, *|UNSUB|*, %unsubscribe_url% — depending on the platform. Do not hand-roll tokens when the platform already signs them.

What is the List-Unsubscribe header and why does it matter more?#

The List-Unsubscribe header is metadata the recipient never sees directly. Gmail, Yahoo, Apple Mail and Outlook read it and render a native unsubscribe affordance in the UI.

Minimum viable header set for a bulk sender:

List-Unsubscribe: <https://example.com/u/8f2a1c9e4b>, <mailto:unsub@example.com?subject=unsubscribe>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

Details that trip people up:

  • Order matters in practice. Put the HTTPS URL first; some clients take the first entry they can act on.
  • List-Unsubscribe-Post turns the URL into a POST target. The mailbox provider sends POST with body List-Unsubscribe=One-Click. Your endpoint must accept POST — a GET-only route silently fails, and the user thinks they unsubscribed when they did not.
  • No authentication on that endpoint. RFC 8058 explicitly forbids requiring a login or extra confirmation.
  • The mailto fallback still helps older clients, but it cannot be your only option under Gmail's bulk sender rules.
  • Two-day processing maximum. Gmail's published requirement is that opt-outs be honored within two days. Same-day is the safe target.

Google's official sender guidelines spell out the thresholds; read them directly rather than trusting a summary blog post, because the numbers have changed twice since 2024.

Which unsubscribe method should you use?#

The right implementation depends on volume, sending stack, and whether you are doing marketing broadcast or one-to-one outbound.

Method What it is Best for Effort Gmail bulk-compliant?
ESP merge tag Platform-generated link + headers Newsletters, drip campaigns Minutes Yes, if headers enabled
Hand-rolled HTML link + token Signed URL you host Custom app transactional/marketing mail 2-4 hours Only with headers added
List-Unsubscribe (mailto) Header pointing to an inbox Legacy systems, low volume 1 hour No, not alone
One-click (RFC 8058) HTTPS POST endpoint + headers Any sender over 5,000/day 3-6 hours Yes
Plain-text opt-out line "Reply 'no thanks' and I'll stop" 1:1 cold outbound, low volume Seconds N/A (not bulk)

For most teams the honest answer is: use your ESP's tag, then verify the headers actually made it into the sent message. That verification step is where the majority of failures hide.

Diagram: Which unsubscribe method should you use
Diagram: Which unsubscribe method should you use

How do you implement one-click unsubscribe on your own endpoint?#

If you send from your own infrastructure, here is the shape of the work.

1. Generate a signed token. Node example:

import crypto from 'node:crypto'

const signToken = (subscriberId, listId, secret) => {
  const payload = `${subscriberId}.${listId}`
  const sig = crypto.createHmac('sha256', secret).update(payload).digest('base64url')
  return `${Buffer.from(payload).toString('base64url')}.${sig}`
}

2. Expose a route that accepts both GET and POST. GET renders a small confirmation page for humans who clicked the footer link. POST — with body List-Unsubscribe=One-Click — processes silently and returns 200 with no body. Never redirect a one-click POST to a login screen.

3. Make the write idempotent. Providers retry. Suppressing an already-suppressed address must return 200, not 409.

4. Write to a global suppression list, not just the campaign list. A recipient who opts out of "Product Updates" and then receives "Company Newsletter" three days later will report spam.

5. Verify the header lands. Send a test to a Gmail account, open the message, choose "Show original," and search for List-Unsubscribe. If it is absent, your sending library stripped it — a common failure with hand-built MIME messages where headers are added after the message is serialized.

6. Log everything. Timestamp, IP, method (link vs header vs reply). Under GDPR you may need to demonstrate that a withdrawal of consent was honored.

Short answer: they need an opt-out mechanism, and in most cases a plain-text sentence is the better choice than a tracked HTML link.

Here is the tension. CAN-SPAM in the US requires a functioning opt-out on commercial email, including cold B2B. GDPR and ePrivacy in the EU allow B2B outreach under legitimate interest in many member states, but still require an easy objection route. Canada's CASL is stricter, requiring consent for most commercial electronic messages.

But a big styled footer with tracking pixels and an image-heavy unsubscribe block makes a one-to-one email look like a broadcast — which is exactly the signal that pushes it into Promotions or spam. Practical middle ground used by most experienced outbound teams:

  • One plain sentence at the bottom: "If this is not relevant, reply 'no' and I will not follow up."
  • Honor it within 24 hours, in a global suppression list your whole team shares.
  • Add List-Unsubscribe as a mailto header if your sending tool supports it — invisible to the recipient, useful to the mailbox provider.
  • Skip the image-based footer entirely.

If you are sending genuine one-to-one volume from real mailboxes, this reads human. If you are sending 8,000 identical messages a day, you are a bulk sender regardless of what you call it, and you need the full header stack. Be honest with yourself about which one you are.

Also worth checking: your authentication. An unsubscribe link cannot rescue a domain with a broken SPF record. Run your domain through a SPF checker before you debug anything downstream, and confirm your footer copy is not tripping filters with a spam checker.

One does not simply send bulk mail without List-Unsubscribe header
One does not simply send bulk mail without List-Unsubscribe header

What are the most common unsubscribe mistakes?#

Ranked by how often they show up in real audits:

  1. Header present in the template, absent in the sent message. Caused by MIME assembly order or a transactional API that only forwards a whitelist of headers. Always verify with "Show original."
  2. One-click endpoint that only accepts GET. The provider POSTs, gets a 405, the user sees no error, and keeps receiving mail. Then they complain.
  3. Confirmation pages with a "Are you sure?" step. Legal in body-link flows, forbidden for one-click. Many teams reuse the same route for both and break compliance.
  4. Per-campaign suppression only. Opt-out from one list, still on four others.
  5. Broken tokens after a database migration. Subscriber IDs change, every historical email's link 404s, complaints spike.
  6. Grey-on-grey 8px text. Recipients cannot find it, so they hit the spam button. That button costs you far more than a lost subscriber.

The last one deserves emphasis. An unsubscribe is a clean exit. A spam complaint is a permanent mark against your domain that affects every recipient you will ever mail. Gmail's threshold is a 0.3% complaint rate, and it is not forgiving. Making the unsubscribe easier to find is the cheapest email deliverability fix available.

Diagram: What are the most common unsubscribe mistakes
Diagram: What are the most common unsubscribe mistakes

Run this checklist on every template, every quarter:

Test How Pass criteria
Header present Gmail "Show original", search List-Unsubscribe Both URL and mailto entries visible
One-click works curl -X POST <url> -d "List-Unsubscribe=One-Click" HTTP 200, address suppressed
Native button renders Send to Gmail + Yahoo test accounts Unsubscribe control appears by sender name
Body link works Click from a mobile client Suppresses in under 3 seconds, no login
Global suppression Trigger a different campaign to the same address Message not delivered
Token integrity Use a 6-month-old test email Link still resolves

Test on mobile specifically. A large share of opens happen on phones, and a footer link that is tappable on desktop can be a 20px target on a phone. If people cannot tap it, they report spam.

Diagram: How do you test an unsubscribe link before launch
Diagram: How do you test an unsubscribe link before launch

Where does list quality fit into this?#

Unsubscribe mechanics are the last mile. Most complaint problems start much earlier — at acquisition.

If a meaningful slice of your list consists of guessed addresses, scraped contacts, or role accounts like info@, you will generate bounces and complaints no matter how elegant your unsubscribe flow is. Mailbox providers weigh bounce rate, complaint rate, and engagement together. A clean 4,000-contact list outperforms a dirty 40,000-contact list on revenue and on reputation.

Two habits fix most of it:

  • Verify before you import. Run every new batch through an email verifier to strip invalid, disposable, and role-based addresses. Catch-all domains need their own treatment — a catch-all verifier gives you a confidence score instead of a coin flip.
  • Source addresses properly in the first place. Finding a verified work address from a name and domain beats permutating six guesses and mailing all of them. Tools like Tomba, Hunter, and BookYourData each approach this differently — Tomba and Hunter start from a domain or a name, BookYourData sells pre-built verified B2B lists by filter. All three beat guessing.

For sourcing volume, Tomba pricing starts with a free tier at 25 searches per month, then Starter at $49/mo, Growth at $99/mo, and Pro at $249/mo. Compare the per-verified-contact cost, not the headline monthly price — a cheaper plan that returns unusable addresses is more expensive once you count the complaint damage. Peer reviews on G2 are a reasonable sanity check before you commit to any of them.

Diagram: Where does list quality fit into this
Diagram: Where does list quality fit into this

What does a compliant setup look like end to end?#

Putting it together, a sender in good standing has:

  1. Authentication — SPF, DKIM, and DMARC all passing, aligned to the From domain.
  2. Headers — List-Unsubscribe with an HTTPS URL first, mailto second, plus List-Unsubscribe-Post for one-click.
  3. Body link — visible, readable, text-based, next to a postal address and a provenance sentence.
  4. Backend — signed tokens, idempotent POST endpoint, global suppression, audit log.
  5. List hygiene — verification at import, re-verification of anything older than six months, sunset policy for non-openers.
  6. Monitoring — Google Postmaster Tools for complaint rate and domain reputation, checked weekly.

Miss any one of those and the others compensate only partially. Miss two and you will see delivery drift within a month.

You can build a textbook-perfect one-click unsubscribe endpoint and still land in spam if the addresses behind it were guessed. The order that works: source verified contacts, verify again before send, then make leaving easy for anyone who wants out.

If sourcing is where your list quality breaks down, the Tomba Email Finder finds professional email addresses by domain, name, or company and returns confidence scores with each result — so the contacts entering your list are ones that can actually receive mail. Start on the free tier, verify a batch you already own, and compare the bounce rate against what you are seeing today. The unsubscribe link matters. The list matters more.

Start your free trial

Ready to find emails that actually work?

Join 150,000+ professionals who stopped guessing and started sending. Free credits on signup — no credit card required.

Get the Tomba newsletter

Practical outbound tactics and product updates — once every two weeks.

Share
0 clapsEnjoyed it? Give a clap.
AU

About the author

Tomba Editorial Team

Was this helpful?

Start finding verified emails today

Join 150,000+ professionals who trust Tomba for accurate contact data. No credit card required.