TryGhost/Ghost · error · Error
No staff invitation email found for ${email}${detail}
Error message
No staff invitation email found for ${email}${detail} What it means
getInvitationToken() calls emailClient.search({subject:'has invited you to join', to:email}). If search() rejects, the catch wraps the underlying error: 'No staff invitation email found for <email>: <detail>'. This is a search-layer failure (mail server unreachable, auth error, IMAP/Graph API exception), not an empty result — the empty-result case is error 114.
Source
Thrown at e2e/helpers/playwright/fixture.ts:200
analyticsPage.header.waitFor({state: 'visible'}),
billingIframe.waitFor({state: 'visible'})
]);
return {
page,
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;
}View on GitHub (pinned to 47d8b0e2ad)
Solutions
- Read detail in the message — it carries the original error (auth, timeout, connection refused).
- Verify the mail server fixture (mailServer) is running and reachable before the test.
- Confirm EmailClient credentials/config match the running mail backend.
- Retry the search after a short backoff for transient mail-server hiccups.
Example fix
// before
try {
messages = await emailClient.search({subject:'has invited you to join', to:email});
} catch (error) {
throw new Error(`No staff invitation email found for ${email}${detail}`);
}
// after
for (let i = 0; i < 3; i++) {
try {
messages = await emailClient.search({subject:'has invited you to join', to:email});
break;
} catch (e) {
if (i === 2) throw new Error(`Mail search failed for ${email}: ${e.message}`);
await new Promise(r => setTimeout(r, 1000));
}
} Defensive patterns
Strategy: try-catch
Validate before calling
async function mailReachable(c: EmailClient): Promise<boolean> {
try { await c.search({subject: 'healthcheck', to: 'nobody@example.com'}); return true; }
catch { return false; }
} Try / catch
let messages;
for (let i = 0; i < 3; i++) {
try {
messages = await emailClient.search({subject:'has invited you to join', to:email});
break;
} catch (e) {
if (i === 2) throw new Error(`Mail search failed for ${email}: ${e.message}`, {cause: e});
await new Promise(r => setTimeout(r, 1000));
}
} Prevention
- Start and verify the mail fixture before tests that depend on invitations.
- Keep MAIL_* env in sync with the running mail backend.
- Retry transient mail-server failures before surfacing them as invite failures.
When it happens
Trigger: Mail server (Mailgun/test mailhog) is down or returns an error. EmailClient auth invalid. Network/timeout contacting the mail backend. Search query shape unsupported by the configured mail provider.
Common situations: Mailgun server fixture not started; wrong MAIL_* env config; mail API credentials expired; transient network failure to the mail backend in CI.
Related errors
- No staff invitation email found for ${email}
- No invitation token found in URL for ${email}
- Please verify your email settings
- email.error
- No sign-in email found for ${member.email}
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/299b721bcd632375.
Report an issue: GitHub.