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

  1. Log the full matched `magicLink` value to see what was actually captured.
  2. Check whether the email template still emits `?token=...` inline and is not HTML-escaped.
  3. Clear stale emails in Mailpit so an old-format message is not matched.
  4. 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

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


AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13). Data as JSON: /api/errors/fd2eacf4ce18ab9e. Report an issue: GitHub.