RocketChat/Rocket.Chat · error · Meteor.Error
error-missing-unsubscribe-link
error-missing-unsubscribe-link
Error message
You must provide the [unsubscribe] link.
What it means
Mailer.sendMail sends bulk mail and requires the body template to contain the literal [unsubscribe] placeholder so a per-recipient unsubscribe URL can be injected; if body.indexOf('[unsubscribe]') === -1 it throws before querying any users. This is a compliance guard - mass mail must always carry an opt-out link.
Source
Thrown at apps/meteor/server/lib/notifications/mail-messages/functions/sendMail.ts:29
import * as Mailer from '../../email/api';
export const sendMail = async function ({
from,
subject,
body,
dryrun,
query,
}: {
from: string;
subject: string;
body: string;
dryrun?: boolean;
query?: string;
}): Promise<void> {
Mailer.checkAddressFormatAndThrow(from, 'Mailer.sendMail');
if (body.indexOf('[unsubscribe]') === -1) {
throw new Meteor.Error('error-missing-unsubscribe-link', 'You must provide the [unsubscribe] link.', {
function: 'Mailer.sendMail',
});
}
let userQuery: Filter<any> = { 'mailer.unsubscribed': { $exists: 0 } };
if (query) {
userQuery = { $and: [userQuery, EJSON.parse(query)] };
}
if (dryrun) {
const user = await Users.findOneByEmailAddress(from);
if (!user) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
function: 'Mailer.sendMail',
});
}
View on GitHub (pinned to b2c16d5842)
Solutions
- Add the [unsubscribe] token where the opt-out belongs, e.g. <a href="[unsubscribe]">Unsubscribe</a>
- Save the template and re-run with dryrun: true to confirm the token resolves to a URL
- Check the body programmatically for the token before launching a campaign
Example fix
// before const body = '<p>News!</p><a href="https://example.com/unsub">Unsubscribe</a>'; // after const body = '<p>News!</p><a href="[unsubscribe]">Unsubscribe</a>;
Defensive patterns
Strategy: validation
Validate before calling
if (!body.includes('[unsubscribe]')) {
throw new Error('template is missing the [unsubscribe] placeholder');
}
await Mailer.sendMail({ from, subject, body, dryrun: true }); Try / catch
try {
await Mailer.sendMail({ from, subject, body });
} catch (err) {
if (err instanceof Meteor.Error && err.error === 'error-missing-unsubscribe-link') {
// add <a href="[unsubscribe]">Unsubscribe</a> to the template body
} else throw err;
} Prevention
- Always author Mailer bodies with the [unsubscribe] token, not a hardcoded link
- Dry-run every new/edited template before a real campaign
- Add a CI/lint check on stored templates for required placeholders
When it happens
Trigger: Calling Mailer.sendMail({ from, subject, body }) where the body only uses [name]/[email] placeholders, or where an unsubscribe hyperlink was hardcoded instead of using the [unsubscribe] token.
Common situations: HTML templates edited under Administration -> Mailer where the placeholder got deleted; templates migrated between workspaces that dropped the token; custom newsletter markup that links to a generic unsubscribe page.
Related errors
- invalid email
- error-invalid-user
- error-ai-not-enabled
- error-ai-provider-not-configured
- error-ai-provider-empty-response
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/bf17677762ab48c8.
Report an issue: GitHub.