kriasoft/react-starter-kit · error · Error
Failed to send email: ${error instanceof Error ? error.messa
Error message
Failed to send email: ${error instanceof Error ? error.message : "Unknown error"} What it means
The catch-all in sendEmail wraps ANY failure from the try block — including the Resend API error thrown above it and network/fetch failures — into 'Failed to send email: <inner message>' with the original attached as `cause`. The generic outer message plus a nested cause means callers often see the same failure twice in the chain.
Source
Thrown at apps/api/lib/email.ts:72
try {
const result = await resend.emails.send({
from: options.from || env.RESEND_EMAIL_FROM,
to: options.to,
subject: options.subject,
html: options.html,
text: options.text,
});
if (result.error) {
throw new Error(
`Resend API error: ${result.error.message || result.error.name || "Unknown error"}`,
);
}
return result;
} catch (error) {
throw new Error(
`Failed to send email: ${error instanceof Error ? error.message : "Unknown error"}`,
{ cause: error },
);
}
}
/**
* Send email verification message.
*
* @param env Environment variables
* @param options User and verification URL (should be time-limited, signed token)
*/
export async function sendVerificationEmail(
env: TemplateEnv,
options: {
user: { email: string; name?: string };
url: string;
},View on GitHub (pinned to 0aa7603435)
Solutions
- Inspect error.cause — the original exception (including the 'Resend API error: ...' message) is preserved there
- Check network egress from the Worker environment and Resend status for outages
- Differentiate the wrapping: match on cause or rethrow the provider error as-is so callers can distinguish validation vs network vs provider failures
- Add retry with exponential backoff for transient (network/5xx) failures before surfacing to the user
Example fix
// before
catch (error) {
throw new Error(`Failed to send email: ${error instanceof Error ? error.message : "Unknown error"}`, { cause: error });
}
// after
catch (error) {
if (error instanceof Error && error.message.startsWith("Resend API error:")) throw error; // don't double-wrap
throw new Error(`Failed to send email: ${error instanceof Error ? error.message : "Unknown error"}`, { cause: error });
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!env.RESEND_API_KEY || !env.RESEND_EMAIL_FROM) {
throw new Error('Email provider not configured (RESEND_API_KEY / RESEND_EMAIL_FROM missing)');
} Type guard
function isSendFailure(error: unknown): error is Error & { cause?: unknown } {
return error instanceof Error && error.message.startsWith('Failed to send email:');
} Try / catch
try {
await sendEmail(env, options);
} catch (error) {
const root = error instanceof Error && error.cause instanceof Error ? error.cause : error;
console.error('Email send failed:', root); // read cause for the real reason
const retryable = !(root instanceof Error && root.message.startsWith('Resend API error:'));
if (retryable) await backoffRetry(() => sendEmail(env, options), 3);
} Prevention
- Always inspect error.cause when handling this error — the outer message is generic
- Retry only network/5xx-shaped failures, never validation or provider 4xx config errors
- Queue emails (durable outbox) instead of sending inline so transient failures can be retried
- Alert on send failures rather than silently swallowing them
When it happens
Trigger: Any sendEmail call where resend.emails.send() rejects (network timeout, Workers fetch failure, DNS, TLS) or where the inner result.error branch threw — the wrapper re-wraps that too.
Common situations: Cloudflare Worker egress blocked or outbound fetch hiccup, Resend returning a 5xx, or a developer logging only error.message and missing the detailed `cause` with the real reason.
Related errors
AI-assisted analysis of kriasoft/react-starter-kit@0aa7603435 (2026-08-31).
Data as JSON: /api/errors/9b65d1e68a572fdc.
Report an issue: GitHub.