Eventually our subscribers won’t want our email in their inbox any more.
They can stop the mail either by unsubscribing from it, or by marking it as spam. We’d far rather they do the first so we should make it as easy as possible for them to unsubscribe.
Also in most jurisdictions you’re legally required to offer a functional, easy to use unsubscription channel. So, how to do that? There are a few different ways to accept unsubscription requests, and most legitimate bulk mail should offer several of them.
Reply to unsubscribe
The recipient replies to the email, the person handling replies removes them from the list. This is a problematic way of handling unsubscription requests.
On the one hand, sending mail that recipients can’t reply to, or where replies are bounced, responded to automatically or silently discarded is fairly recipient-hostile. It’s better if those replies go to a customer support team, and when they do you want to empower the customer support staff to remove recipients from the list. Using this for unsubscription is expensive and kludgy and not quite as reliable or robust as you might like. And you can’t just ignore those unsubscription requests and require your customer retention folks have a conversation with the recipient before you unsubscribe them – both because that breaks the law in many jurisidictions and because ain’t nobody got time for that, they’ll just hit the “This is Spam” button and move on.
On the other hand spammers – especially B2B spammers who’ve fallen for “growth hacker” snake oil – love it. They think that forcing a recipient to reply to unsubscribe will count as “engagement” and improve the delivery of the rest of their spam. And that encouraging recipients to reply will open a channel for them to pitch whatever their crappy service is.
That means a lot of bad B2B spam doesn’t offer any way to unsubscribe other than a cutesy suggestion you mail them back, e.g.
- “P.S. If you don’t want to hear from me anymore, just let me know”
- “P.S. If you’d rather I didn’t reach out, just reply with “no.” No worries! 😊”
- “Reply “No thanks” if you would like to no longer receive messages from me. Thank you.”
- “Don’t want emails from us anymore? Reply to this email with the word “UNSUBSCRIBE” in the subject line.”
- If you don’t wish to receive further emails kindly reply with “Unsubscribe”
- “Reply with “not interested” if you want us to stop reaching out”
- “To opt out of further communication, please reply to this email with “opt out.””
- “Reply with Unsub.”
So unless you want to look like a bad B2B spammer, don’t advertise replying as a method of unsubscribing. It looks tacky and untrustworthy, people don’t trust it and will just report it as spam, and content filters will pay attention too. And handling unsubscribes that way is more expensive than a self-service approach. And definitely don’t have it as the sole way to unsubscribe.
But if you accept replies then give your customer support folks who handle them the power to unsubscribe someone when asked.
Click Here To Unsubscribe
This is the little link in the footer of your email that everyone who’s been online for more than twenty minutes will recognise as a way to unsubscribe. If you’re mailing through an ESP they’ll provide a nice, robust implementation of this and either make it easy for you to use, or make it mandatory. As an ESP customer let them handle it.
If you’re implementing it yourself, though, there are some subtleties to consider.
Requiring the user provide their email address
US CAN-SPAM law does allow the unsubscribe link to go to a web page with a text entry box, where the recipient is required to enter their email address and click a button to be unsubscribed. This was put in to allow existing mailing list managers, some of them dating back to the mid-80s, to keep working the way they’d been working successfully for decades.
But it’s 2023. Don’t do that. It’s user-hostile – requiring the user to do more work. It’s not robust – if a user typos their email address or enters the wrong email address then either they’ll have to deal with an error message or, worse, their attempt to unsubscribe will silently fail.
And there’s no way to be sure that the email address they give you is their email address. Someone malicious may give you someone else’s email address. Sure, you could send that email address an email asking them to confirm they want to unsubscribe, but that would violate CAN-SPAM – and if you’re going to spend effort to do something like that, why not just do it right to begin with.
You should always know from the moment a recipient clicks their email address (and in more complex setups, which list they want to unsubscribe from).
Your first idea might be to include the users email address in the link – “https://firstname.lastname@example.org”. That provides the information you need, but you should use an opaque cookie instead. That will give you the same information, but avoid leaking their email address to log files, third party vendors and so on.
Click and Gone
So the moment someone clicks on your unsubscription link you know their email address. You could just immediately unsubscribe them, but that’s not a great idea.
The user might click on the unsubscribe link accidentally, especially if it’s in a pale grey 4 pt footer at the bottom of the email (don’t do that). They might be looking for an unsubscription center rather than immediately wanting to unsubscribe.
And, more importantly, many pieces of software will follow links in an email message. Spam and malware filters may do so before it’s even delivered. If following that link causes an immediate unsubscription you may end up losing a lot of subscribers who didn’t want to leave.
There are ways you can try and filter out those automated clicks, but for unsubscription it’s better to have the link go to a landing page. On that landing page you should have a big, obvious “Unsubscribe Me” button, that POSTs a form to unsubscribe a user. A POST – submitting a web form – is something that’s intended to take an action, making a change to data. Because of that any well-intentioned bot won’t click on your “Unsubscribe Me” button.
Another advantage of the landing page is that you can tell the user which list they’re unsubscribing from, and (maybe1there’s a tradeoff between user convenience and privacy, so there’s not one best practice) what email address will be unsubscribed. That can help avoid mistakes, but also provides trust in the process.
And while you have to have a button that will do the unsubscribe on that page you can have other options too. Maybe an “opt-down” where they can reduce the volume of mail without unsubscribing altogether. Maybe an unsubscription center where they can unsubscribe just from topics they’re not interested in. Maybe a text box that asks why they’re unsubscribing, as long as it’s very clear that it’s optional.
This approach, with a landing page is a “one-click unsubscribe”, and is compliant with CAN-SPAM. Some folks use the “one-click” term to describe the (terrible) “click-and-gone” practice. But whatever you call it, it’s fine, it complies with CAN-SPAM and is common practice.
As we don’t want to require the user provide an email address we’re including information about their email address in the link they click in the mail. And because we want to minimise the places we have PII we’re using a cookie.
The simplest sort of cookie in an unsubscribe link might look like “https://example.com/?unsub=1234”, where “1234” is the cookie. 1234 might be the primary key in our database, so we get the click, go to our database and look up row 1234 and get back the email address email@example.com. That’s great until we look at malicious behaviour.
What if I go to “https://example.com/?unsub=1235”? That’ll let me unsubscribe someone that’s not me. If you’re displaying information on the landing page it might let me know their name, or their email address too. Someone malicious could easily iterate through a big range of numbers and harvest information from the landing page for each one and, if they wanted to make our lives more difficult, unsubscribe them.
So we need some way to ensure that someone following a link to our unsubscribe handler is in possession of an email we sent, with that link in it. That’s the only way we can prove that the person clicking is the recipient.
There are several ways to do this – using a long randomly generated string rather than a small integer is one approach. If we store that long string in our database we can easily use it to look up the recipient, but someone trying to guess at a valid string won’t be able to do it, as for every valid string there will be millions of invalid ones.
There are other approaches, mostly based on cryptography, that can be operationally cheaper to implement but as long as a bad actor can’t guess at a valid link your work here is done.
List-Unsubscribe is a header you can add to your outgoing email. It’s typically not visible to the recipient, but the recipients mail client can take advantage of it to simplify unsubscribing in some cases.
It allows the mail client to offer an “unsubscribe” menu option. Some ISPs may integrate the unsubscribe functionality into other features too, for instance if a recipient marks a message as spam they may (perhaps depending on the reputation of the sender) be offered the chance to unsubscribe instead. Or the “This is Spam” button might be replaced with an Unsubscribe button.
List-Unsubscribe header is described in detail in RFC 2369 but it just contains one or more URLs. Each of those URLs is expected to be either a mailto: URL, where sending mail to the email address in it will unsubscribe the user with no further interaction, or a http / https link that the recipient is expected to open in their browser, going to a landing page in the same way as an unsubscribe link in the body of the message would.
The two options are very different in the way that the action can be represented to the user, not least because the mailto: is non-interactive and doesn’t require leaving the mail client, while the http / https one just opens a browser and expects the user to do everything from there. Because of that support for them may differ at different mailbox providers, e.g. Microsoft at one point only supported mailto: for list-unsubscribe functionality.
If you’re using an ESP they probably provide this functionality and you should just use it, but if you’re rolling your own implementation there are some things to think about.
The https link has the same requirements as I talked about in the “Click here to Unsubscribe” section above, and may well go to the same landing page and work in the same way (though at some point you’re going to want to know who unsubscribed via a link in the body vs a List-Unsubscribe header, so include something to let you tell the difference in your reporting).
The mailto: link will be used to send an email with no additional user interaction, so all the information you need to handle the unsubscribe must be included in the mailto: link in a way that’ll be communicated successfully to your unsubscription handler.
The mailto: URL scheme allows for some useful parameters. You can use ?subject= to set the subject of the mail, or ?body= to include some information in the body of the mail amongst others. Support for these other than ?subject is a bit sparse, and even ?subject might not be supported reliably by List-Unsubscribe handlers.
So your best bet is to include all the information you need to handle the request in the email address itself. That typically means that you’ll include a cookie in the left hand side of the email address, much the same as the cookie you’re using in other unsubscription links. It has the same requirements for opaqueness and authentication, but has one additional one due to it being part of an email address. It shouldn’t be more than 63 characters long (the RFC limit is 64, but I’ve seen enough off-by-one errors causing 64 character long local parts to be mishandled that I’ll limit it to 63). And it should consist of characters that are unlikely to be mangled or misinterpreted. You can check the definition of atext for the details, but it would be safe to use a string consisting solely of A-Z, a-z or 0-9, or to use several strings like that separated by periods.
There are a few ways to handle receiving those emails, but using a dedicated right hand side – e.g. @unsub.example.com) will give you the flexibility to use a dedicated unsubscription handler, either today or as you need to scale.
The requirements are very similar to those for running a dedicated bounce handler that identifies bounces using VERP (variable envelope return path).
Mailbox providers and mail client developers like the List-Unsubscribe header. They like the non-interactiveness of the mailto: variant, as it means their user doesn’t need to leave their mail client. But they don’t like the asynchronous nature of email – you don’t know the unsubscribe has actually happened, what do you do if the mail you sent is later bounced, how do you display that to the user?
So RFC 8058 adds a bag on the side to the List-Unsubscribe header to provide synchronous, non-interactive unsubscription over https in a way that is resistant to bot clicks and other issues. It’s a little inelegant but provides exactly what mailbox providers were looking for. It’s good; you should use it in your email.
It adds an additional header – List-Unsubscribe-Post – that changes the semantics of the List-Unsubscribe header a little.
If an email
- has a
- that header contains the string
List-Unsubscribeheader contains only one https link
- the email is DKIM signed
- the DKIM signature covers the List-Unsubscribe and List-Unsubscribe-Post headers
then the mailbox provider may handle in-mail-client unsubscription requests by doing an https POST to the https URL in the Link-Unsubscribe header. That POST will look like a normal POST from a web browser submitting a form. The contents of the POST aren’t very interesting, but the URL it’s submitted to gives you all the information you need to honor the unsubscription request.
Because List-Unsubscribe-Post uses the URL in the List-Unsubscribe header to use it you also have to offer an unsubscribe landing page at the same URL, so that a mail client that’s not trying to use List-Unsubscribe-Post will open the users browser at that URL and see the unsubscription landing page.
That means the webserver will need to handle POST and GET requests to that URL differently. POSTs will be immediate unsubscription, GETs display the landing page.
If someone is reading replies to your mail, they should have the power to unsubscribe recipients. But you maybe shouldn’t advertise reply to unsubscribe in your mail, and definitely shouldn’t rely on it as your sole unsubscription channel.
You should have a webserver that serves an unsubscription landing page for a recipient, identifying the recipient via an opaque cookie in the URL. That same webserver should handle POST requests to unsubscribe RFC 8058 style at the same URL.
You should include an unsubscribe link in the body of your message, at the bottom of the mail where people expect to find it, pointing at that landing page.
You should have List-Unsubscribe and List-Unsubscribe-Post headers2and while you’re going that, add a List-ID header too. The List-Unsubscribe header should have one, and only one, https link in it pointing at your unsubscription webserver. Your List-Unsubscribe-Post header should contain the “List-Unsubscribe=One-Click” shibboleth.
You should have a second link in the List-Unsubscribe header, a mailto:, pointing at an unsubscribe-by-mail server, to support in-app unsubscribe at providers who don’t support RFC 8058. That included iOS and Microsoft the last time I checked, but that may be out of date.
All your mail should be DKIM signed. The DKIM signature h= field should include both List-Unsubscribe and List-Unsubscribe-Post.
Check that the unsubscription channels work. Sign up a few email addresses for your list. Check that you can unsubscribe via the link in the body. Check to see if, e.g., gmail offers an unsubscription option that it works. If you want to go further you can check that the mailto: link in the List-Unsubscribe header works just by sending an email to it, and you can use curl to check List-Unsubscribe-Post:
curl -X POST -F List-Unsubscribe=One-Click https://your.link.here/