TryGhost/Ghost · error · Error

No staff invitation email found for ${email}

Error message

No staff invitation email found for ${email}

What it means

The search succeeded but returned an empty array: messages[0] is undefined, so no invitation email matched the subject/to filter. This means the invite was never sent, never delivered, or arrived but didn't match the search criteria. Distinct from 113 (search threw) — here the mailbox query worked but found nothing.

Source

Thrown at e2e/helpers/playwright/fixture.ts:206

        context,
        ghostAccountOwner: authenticatedSession.ghostAccountOwner
    };
}

async function getInvitationToken(emailClient: EmailClient, email: string): Promise<string> {
    let messages;

    try {
        messages = await emailClient.search({subject: 'has invited you to join', to: email});
    } catch (error) {
        const detail = error instanceof Error ? `: ${error.message}` : '';
        throw new Error(`No staff invitation email found for ${email}${detail}`);
    }

    const message = messages[0];

    if (!message) {
        throw new Error(`No staff invitation email found for ${email}`);
    }

    const detailedMessage = await emailClient.getMessageDetailed(message);
    const inviteUrl = new URL(extractInviteLink(detailedMessage));
    const token = inviteUrl.pathname.split('/').filter(Boolean).at(-1);

    if (!token) {
        throw new Error(`No invitation token found in URL for ${email}`);
    }

    return token;
}

async function createStaffAccount(page: Page, emailClient: EmailClient, role: AssignableStaffRoleName): Promise<StaffAccount> {
    const staffAccountFactory = createStaffAccountFactory(
        page.request,
        email => getInvitationToken(emailClient, email)
    );

View on GitHub (pinned to 47d8b0e2ad)

Solutions

  1. Poll the mailbox with a timeout instead of a single query — invitations often arrive with a few seconds lag.
  2. Verify the staff invite API call returned success and used the same email being queried.
  3. Confirm the subject filter still matches the current Ghost invitation template; update if it changed.
  4. Check the mail backend actually received the message (search by recipient, not just subject).

Example fix

// before
const message = messages[0];
if (!message) throw new Error(`No staff invitation email found for ${email}`);

// after
let message;
for (let i = 0; i < 30 && !message; i++) {
    messages = await emailClient.search({subject:'has invited you to join', to:email});
    message = messages[0];
    if (!message) await new Promise(r => setTimeout(r, 1000));
}
if (!message) throw new Error(`No staff invitation email found for ${email} after 30s`);
Defensive patterns

Strategy: retry

Validate before calling

async function inviteArrived(c: EmailClient, email: string): Promise<boolean> {
    const m = await c.search({subject:'has invited you to join', to:email});
    return m.length > 0;
}

Try / catch

let message;
for (let i = 0; i < 30 && !message; i++) {
    const m = await emailClient.search({subject:'has invited you to join', to:email});
    message = m[0];
    if (!message) await new Promise(r => setTimeout(r, 1000));
}
if (!message) throw new Error(`No staff invitation email found for ${email} after 30s`);

Prevention

When it happens

Trigger: Staff invite API call never actually sent the email. Email delivered to a different address than queried. Subject line changed in a Ghost version so the 'has invited you to join' filter misses. Mail delivery lag — the test queried before the message arrived.

Common situations: Mail delivery latency in CI; invitation API silently failed; wrong email address fixture; subject template changed in a Ghost release; mail server indexing delay.

Related errors


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