TryGhost/Ghost · warning · Error
Magic link missing token parameter
Error message
Magic link missing token parameter
What it means
Thrown by `extractMagicLink` after a magic-link regex match is found but the matched URL does not contain `token=`. In practice the regex itself requires `\?token=`, so this branch is a defensive belt-and-suspenders guard; reaching it implies the email body contained a near-matching URL that the regex captured without a real token segment.
Source
Thrown at e2e/helpers/services/email/utils.ts:18
import baseDebug from '@tryghost/debug';
import {EmailMessageDetailed} from './mail-pit';
const debug = baseDebug('e2e:helpers:utils:email');
// Look for magic link pattern in the email message body
// Ghost magic links typically look like: http://localhost:30000/members/?token=...&action=signup
export function extractMagicLink(emailMessageBody: string, expectedActionInUrl: 'signup' | 'signin' = 'signup'): string {
const magicLinkRegex = /https?:\/\/[^\s]+\/members\/\?token=[^\s&]+(&action=\w+)?(&r=[^\s]+)?/gi;
const matches = emailMessageBody.match(magicLinkRegex);
if (matches && matches.length > 0) {
const magicLink = matches[0];
debug(`Found magic link: ${magicLink}`);
// Validate that the link has required parameters
if (!magicLink.includes('token=')) {
throw new Error('Magic link missing token parameter');
}
if (!magicLink.includes(`action=${expectedActionInUrl}`)) {
throw new Error(`Magic link missing action=${expectedActionInUrl} parameter`);
}
return magicLink;
}
throw new Error('No magic link found in email');
}
export function extractPasswordResetLink(message: EmailMessageDetailed): string {
const html = message.HTML || '';
const match = html.match(/href="([^"]*\/ghost\/reset\/[^"]+)"/);
if (!match) {
throw new Error(`No reset URL found in email HTML`);View on GitHub (pinned to 47d8b0e2ad)
Solutions
- Log the full matched `magicLink` value to see what was actually captured.
- Check whether the email template still emits `?token=...` inline and is not HTML-escaped.
- Clear stale emails in Mailpit so an old-format message is not matched.
- Tighten the regex or add `&action=` expectation to avoid matching non-link fragments.
Defensive patterns
Strategy: validation
Validate before calling
import {extractMagicLink} from '@/helpers/services/email/utils';
try {
const link = extractMagicLink(body, 'signin');
new URL(link); // throws if malformed
} catch (err) {
console.error('Magic link extraction failed:', (err as Error).message);
} Type guard
function hasTokenParam(url: string): boolean {
try { return new URL(url).searchParams.has('token'); } catch { return false; }
} Prevention
- Clear stale Mailpit messages before a run so old-format emails are not matched.
- Log the matched URL when debugging to detect truncation/escaping.
- Confirm the magic-link email template still emits `?token=` inline.
When it happens
Trigger: The email body matches the magic-link URL regex but the captured URL lacks a `token=` query parameter — e.g., a truncated/escaped link, a preview-text fragment that looks like a link, or an email template change that altered the link format.
Common situations: Email client or Mailpit escaped/truncated the URL; the magic-link template changed and no longer includes the token inline; a stale cached email with a different format matched the broad regex; HTML entity encoding broke the query string.
Related errors
- No sign-in email found for ${member.email}
- Failed to send magic link email
- Invalid donation amount: ${value}
- No staff invitation email found for ${email}${detail}
- No staff invitation email found for ${email}
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/fd2eacf4ce18ab9e.
Report an issue: GitHub.