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

  1. Inspect error.cause — the original exception (including the 'Resend API error: ...' message) is preserved there
  2. Check network egress from the Worker environment and Resend status for outages
  3. Differentiate the wrapping: match on cause or rethrow the provider error as-is so callers can distinguish validation vs network vs provider failures
  4. 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

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.