Appearance
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
| Flow | Trigger | Typical from | Notes |
|---|---|---|---|
| Email verification | sendVerificationEmail in src/actions/email-verification-action.ts | appConfig.resend.fromNoReply (default) | Link uses appConfig.domainUrl (/verify-email?token=…). |
| Password reset | forgotPasswordAction in src/actions/password-reset-actions.ts | fromNoReply | OAuth-only accounts get the same ambiguous success message without sending mail (/reset-password?token=…). |
| Stripe order confirmation | checkout.session.completed in src/app/api/stripe/route.ts | fromNoReply | Sends only when the paid Price ID matches a plan in appConfig.stripe.plans; failures are swallowed so the webhook still succeeds. |
| Contact form | submitContactAction in src/actions/contact-actions.ts | fromNoReply | Delivers 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)
- Create a Resend account.
- Domains — In the dashboard, add your sending domain and complete DNS (SPF/DKIM, etc.). Use Verify once records propagate.
- API key — API 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 asappConfig.resend.resendKey. If missing,browserConsoleErrorlogs that the key is required (src/lib/resend.ts).AUTH_EMAIL_FROM_NOREPLY/AUTH_EMAIL_FROM_ADMIN— Map tofromNoReply/fromAdmininsrc/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:
| Field | Source / default | Purpose |
|---|---|---|
fromNoReply | AUTH_EMAIL_FROM_NOREPLY or NexusOrbit \<onboarding@resend.dev\> | Default from in sendEmail. |
fromAdmin | AUTH_EMAIL_FROM_ADMIN or same Resend onboarding default | Reserved for admin/marketing flows (not wired in sendEmail yet). |
supportEmail | Hardcoded in config.ts today — change this to your support inbox | Shown on the Contact page (contact-details.tsx), mail layouts, and contact-actions recipient (replyTo = submitter). |
resendKey | AUTH_RESEND_KEY | Passed 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).
Domain URL in links
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
| Role | Path |
|---|---|
| Send + render helpers | src/lib/resend.ts |
| Types | src/types/resend.ts |
| App + Resend settings | src/config.ts |
| Verification emails | src/actions/email-verification-action.ts, src/components/mails/verification-email.tsx |
| Password reset | src/actions/password-reset-actions.ts, src/components/mails/password-reset-email.tsx |
| Order confirmation | src/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.