Skip to content

Email with Resend

The app ships with Resend as the default mail provider (resend package). Sending is centralized in src/lib/resend.ts: sendEmail wraps resend.emails.send, and renderEmail turns React email components into HTML (@react-email/components).

TIP

You can replace Resend later by swapping the implementation behind sendEmail; callers should keep using @/lib/resend types (src/types/resend.ts).

What uses email today

FlowTriggerTypical fromNotes
Email verificationsendVerificationEmail in src/actions/email-verification-action.tsappConfig.resend.fromNoReply (default)Link uses appConfig.domainUrl (/verify-email?token=…).
Password resetforgotPasswordAction in src/actions/password-reset-actions.tsfromNoReplyOAuth-only accounts get the same ambiguous success message without sending mail (/reset-password?token=…).
Stripe order confirmationcheckout.session.completed in src/app/api/stripe/route.tsfromNoReplySends only when the paid Price ID matches a plan in appConfig.stripe.plans; failures are swallowed so the webhook still succeeds.
Contact formsubmitContactAction in src/actions/contact-actions.tsfromNoReplyDelivers to appConfig.resend.supportEmail (fallback string in code if empty); replyTo set to the visitor’s email.

appConfig.resend.fromAdmin is defined in src/config.ts for future use but is not referenced by sendEmail yet; transactional mail uses fromNoReply unless a call passes from: explicitly.

Prerequisites (Resend)

  1. Create a Resend account.
  2. Domains — In the dashboard, add your sending domain and complete DNS (SPF/DKIM, etc.). Use Verify once records propagate.
  3. API keyAPI Keys → create a key. Copy it once and store it in your secrets manager / hosting env (not in git).

Production senders (From:) must use an address/domain Resend accepts — either Resend’s onboarding@resend.dev (testing) or your verified domain.

Environment variables

env
# Required for any real sending (without it, Resend client is null and sends return an error).
AUTH_RESEND_KEY=re_xxxxxxxxxxxx

# Optional — override default From (see fallback below).
AUTH_EMAIL_FROM_NOREPLY=NexusOrbit <noreply@yourdomain.com>
AUTH_EMAIL_FROM_ADMIN=NexusOrbit <billing@yourdomain.com>
  • AUTH_RESEND_KEY — Read as appConfig.resend.resendKey. If missing, browserConsoleError logs that the key is required (src/lib/resend.ts).
  • AUTH_EMAIL_FROM_NOREPLY / AUTH_EMAIL_FROM_ADMIN — Map to fromNoReply / fromAdmin in src/config.ts.

WARNING

Do not put secrets in markdown or commit .env. Use .env.local locally and your host’s env UI in production.

Configuration in this project

All Resend-related settings live under appConfig.resend in src/config.ts:

FieldSource / defaultPurpose
fromNoReplyAUTH_EMAIL_FROM_NOREPLY or NexusOrbit \<onboarding@resend.dev\>Default from in sendEmail.
fromAdminAUTH_EMAIL_FROM_ADMIN or same Resend onboarding defaultReserved for admin/marketing flows (not wired in sendEmail yet).
supportEmailHardcoded in config.ts todaychange this to your support inboxShown on the Contact page (contact-details.tsx), mail layouts, and contact-actions recipient (replyTo = submitter).
resendKeyAUTH_RESEND_KEYPassed to new Resend(resendKey).

Align supportEmail with a mailbox you monitor. For from* addresses, use identities allowed on your verified domain (e.g. Nexus Orbit \<noreply@yourdomain.com\>).

Sending from code

Minimal pattern:

ts
import { renderEmail, sendEmail } from "@/lib/resend";
import MyEmail from "@/components/mails/my-email";

const html = await renderEmail(MyEmail, { name: "Jane" });

const result = await sendEmail({
  to: "jane@example.com",
  subject: `Hello — ${appConfig.appName}`,
  html,
  // optional: from: appConfig.resend.fromAdmin, replyTo: "support@yourdomain.com"
});

if (!result.success) {
  // handle result.error
}

sendEmail validates to and replyTo formats (src/lib/utils.ts isValidEmail).

Verification and reset links use appConfig.domainUrl (AUTH_URL / NEXT_PUBLIC_APP_URL / VERCEL_URL chain in src/config.ts). Wrong values produce broken links in email — verify for staging and production.

Optional: other providers

Replacing Resend means implementing the same sendEmail / renderEmail behavior (or keeping renderEmail and only swapping the HTTP send). contact-actions additionally requires TURNSTILE_SECRET_KEY for Cloudflare Turnstile; that is separate from Resend.

File map

RolePath
Send + render helperssrc/lib/resend.ts
Typessrc/types/resend.ts
App + Resend settingssrc/config.ts
Verification emailssrc/actions/email-verification-action.ts, src/components/mails/verification-email.tsx
Password resetsrc/actions/password-reset-actions.ts, src/components/mails/password-reset-email.tsx
Order confirmationsrc/app/api/stripe/route.ts, src/components/mails/order-confimation-email.tsx (verify actual filename if different)
Filename note

Stripe route imports OrderConfirmationEmail from @/components/mails/order-confirmation-email — align templates under src/components/mails/ when documenting file lists.

Built with Nexus Orbit