Email Unsubscribe Management System
Every marketing email that lacks an easy unsubscribe is a risk of receiving a spam complaint. One click on the "This is spam" button in Gmail or Outlook triggers a chain: algorithms detect low sender reputation, the domain enters blacklists, and email deliverability plummets to zero. For an e-commerce store with a monthly turnover of 50 million rubles, losing the email channel means a lost profit of 1-2 million rubles per month. The system implementation cost ranges from 50,000 to 150,000 rubles depending on complexity. We build an unsubscribe system that is legally flawless (GDPR, FZ-152) and technically transparent: one-click link, List-Unsubscribe, preference management. No data black holes and no security compromises.
Key Problems and Risks
The first and main risk is legal. Lack of easy unsubscribe is a direct violation of Article 17 GDPR ("Right to erasure") and Russian FZ-152. Fines can reach 4% of annual turnover, which for medium-sized businesses amounts to millions of rubles. Recently, a large e-commerce project received a Roskomnadzor order for 2.5 million rubles precisely due to an opaque unsubscribe procedure.
The second problem is email server blocking. Gmail and Outlook require the List-Unsubscribe header for senders with volumes above 5,000 emails per day. Without it, emails land in spam, and domain reputation degrades within a week—deliverability drops by 10% every campaign.
The third is user experience. A complex unsubscribe procedure (requiring login, multiple clicks) provokes mass complaints. A Return Path study found that 68% of users click "Spam" if they don't see an unsubscribe button within three seconds. And each percentage point of complaints lowers sender reputation by 5-10 points.
How Does One-Click Unsubscribe Work?
One-click unsubscribe is built on the List-Unsubscribe header (RFC 8058). Upon receiving an email, the mail client shows an "Unsubscribe" button. The user does not need to open a browser or enter a password—a POST request goes to the server, and the unsubscribe is processed instantly. Under the hood, an HMAC-signed token prevents forgery and allows exact identification of what the user is unsubscribing from.
import { createHmac } from 'crypto';
function generateUnsubscribeToken(userId: string, type: string): string {
const payload = `${userId}:${type}:${Date.now()}`;
const signature = createHmac('sha256', process.env.UNSUBSCRIBE_SECRET!)
.update(payload)
.digest('hex');
return Buffer.from(`${payload}:${signature}`).toString('base64url');
}
function generateUnsubscribeUrl(userId: string, type: string = 'all'): string {
const token = generateUnsubscribeToken(userId, type);
return `/unsubscribe/${token}`;
}
Token verification details
The token is decoded, the HMAC signature is verified, and the expiration time (no more than 48 hours) is checked. If the token is invalid, the user sees an error page and no unsubscribe occurs.
Why Is List-Unsubscribe Important?
Without List-Unsubscribe, emails are highly likely to land in spam. Major email providers—Gmail, Outlook, Yandex—analyze headers and penalize senders without this field. Reputation penalty: every million emails without List-Unsubscribe can cost 10-15% deliverability. For a project with 100,000 subscribers, this means losing 10,000-15,000 contacts per single campaign. One-click unsubscribe is 5x faster than mailto and 10x more user-friendly.
How We Do It: Stack and Approach
We use a database with subscription type separation and a suppression list. Our team has implemented this system for over 30 businesses across retail and SaaS, with 5+ years of experience in email infrastructure.
Example structure:
CREATE TABLE email_subscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
subscription_type VARCHAR(100) NOT NULL,
-- 'marketing', 'digest', 'product_updates', 'security', 'transactional'
is_active BOOLEAN NOT NULL DEFAULT true,
unsubscribed_at TIMESTAMPTZ,
unsubscribe_reason TEXT,
created_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (user_id, subscription_type)
);
CREATE TABLE email_suppression_list (
email VARCHAR(255) PRIMARY KEY,
reason VARCHAR(100) NOT NULL, -- 'unsubscribe', 'bounce', 'complaint'
created_at TIMESTAMPTZ DEFAULT now()
);
The unsubscribe handler accepts GET (show page) and POST (confirm):
// GET /unsubscribe/:token — link in email (one-click preview)
app.get('/unsubscribe/:token', async (req, res) => {
const decoded = parseUnsubscribeToken(req.params.token);
if (!decoded) return res.status(400).render('unsubscribe-invalid');
res.render('unsubscribe', {
userId: decoded.userId,
type: decoded.type,
token: req.params.token,
});
});
// POST /unsubscribe/:token — confirm unsubscribe
app.post('/unsubscribe/:token', async (req, res) => {
const decoded = parseUnsubscribeToken(req.params.token);
if (!decoded) return res.status(400).json({ error: 'Invalid token' });
const { type, reason } = req.body;
if (type === 'all') {
await db.query(
`UPDATE email_subscriptions
SET is_active = false, unsubscribed_at = now(), unsubscribe_reason = $1
WHERE user_id = $2 AND subscription_type != 'transactional'`,
[reason, decoded.userId]
);
const user = await db.users.findById(decoded.userId);
await db.query(
`INSERT INTO email_suppression_list (email, reason) VALUES ($1, 'unsubscribe')
ON CONFLICT (email) DO NOTHING`,
[user.email]
);
} else {
await db.query(
`UPDATE email_subscriptions
SET is_active = false, unsubscribed_at = now()
WHERE user_id = $1 AND subscription_type = $2`,
[decoded.userId, type]
);
}
res.json({ ok: true });
});
List-Unsubscribe Header and One-Click (RFC 8058)
In addition to the standard mailto header, we add List-Unsubscribe-Post for one-click unsubscribe without opening a browser. This is mandatory for Gmail and Outlook with high sending volumes.
await sendEmail({
to: user.email,
subject: 'Our Digest',
html: emailHtml,
headers: {
'List-Unsubscribe': `<mailto:[email protected]?subject=unsub-${userId}>, <https://app.domain.com/unsubscribe/${token}>`,
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
},
});
// POST /api/email/list-unsubscribe — One-Click handler
app.post('/api/email/list-unsubscribe', async (req, res) => {
const { 'list-unsubscribe': fieldValue } = req.body;
// Field = 'One-Click' — just process
res.status(200).end();
});
Which Subscription Types Should Remain Mandatory?
Transactional and security notifications must not be disableable. Others are user's choice. Here is a typical matrix:
| Type | Description | Disableable |
|---|---|---|
| transactional | Confirmations, invoices | No |
| security | New device login | No |
| product_updates | Product updates | Yes |
| marketing | Promotions and discounts | Yes |
| digest | Weekly digest | Yes |
Comparison of Unsubscribe Methods
| Method | User Action | Processing Speed | Technical Complexity |
|---|---|---|---|
| One-click (RFC 8058) | One click | Instant | Medium |
| Mailto (RFC 2369) | Open email client | Delayed | Low |
| Preference page | Login, selection | 1-2 minutes | High |
Work Process
- Analysis — study current subscription structure, legal requirements, email restrictions.
- Design — database schema, API endpoints, contracts.
- Implementation — code, integration with your system, List-Unsubscribe setup.
- Testing — verify on real mailboxes (Gmail, Outlook, Yandex).
- Deploy and Document — deliver code, API description, admin instructions.
What's Included
- Design of subscription and suppression schema
- Development of one-click link with HMAC token
- Configuration of List-Unsubscribe (RFC 2369 + RFC 8058)
- Preference management page
- Integration with your database
- Testing with email providers
- Documentation and staff training
- Launch support
Estimated Timeline
Basic system (one-click + suppression list) — from 2 to 3 days. With extended preferences and analysis — up to 5 days. Cost is calculated individually based on your architecture.
Evaluate your project: contact us, and we will send a detailed proposal with stages and timelines. Get a consultation on integrating the unsubscribe system. Our solution ensures email marketing compliance with all regulations.







