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

  1. Add the [unsubscribe] token where the opt-out belongs, e.g. <a href="[unsubscribe]">Unsubscribe</a>
  2. Save the template and re-run with dryrun: true to confirm the token resolves to a URL
  3. 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

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


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/bf17677762ab48c8. Report an issue: GitHub.