MuseMVP Docs

Email

Send your first verification email, configure your sender, and customize email templates.

Email enables registration codes, OTP sign-in, password recovery, and contact messages. Configure a real provider when you want these messages delivered to an inbox.

Before You Start

Have a running local app, a test inbox, and a sending domain you can verify. Use Resend for this walkthrough, or configure Cloudflare Email Sending below. Provider limits depend on your account.

1. Connect Your Provider

Node: Resend

In your Resend account, add a sending domain, copy its required DNS records to your DNS provider, and wait for verification. Create an API key authorized to send mail. Set these values in apps/web/.env.local:

MAIL_PROVIDER="resend"
RESEND_API_KEY="YOUR_RESEND_API_KEY"
MAIL_FROM="Acme <[email protected]>"
MAIL_FROM_NEWSLETTER="Acme <[email protected]>"
CONTACT_FORM_TO="[email protected]"
NEXT_PUBLIC_EMAIL_SUPPORT="[email protected]"

Replace example.com with your verified domain. MAIL_FROM is the transactional sender; CONTACT_FORM_TO is the inbox receiving contact messages. Keep the NEWSLETTER_TOKEN_SECRET generated by init.

Cloudflare Email Sending

Configure a sending domain and obtain an API token with the required email-sending access for your account. Set these values in apps/web/.env.local:

MAIL_PROVIDER="cloudflare"
CLOUDFLARE_ACCOUNT_ID="YOUR_ACCOUNT_ID"
CLOUDFLARE_API_TOKEN="YOUR_EMAIL_SENDING_TOKEN"
MAIL_FROM="Acme <[email protected]>"
MAIL_FROM_NEWSLETTER="Acme <[email protected]>"
CONTACT_FORM_TO="[email protected]"
NEXT_PUBLIC_EMAIL_SUPPORT="[email protected]"

Set the same server credentials on your production host. Set the same server credentials on your production host before launch.

2. Receive Your First Email

Restart the dev server. Open the login page and request an email sign-in code for an account you control, or use password recovery for an existing account. Check the inbox and spam folder, then finish that flow with the received code.

For local Node-only experiments, MAIL_PROVIDER=console prints mail in the server terminal without delivery. Switch to a real provider before testing deliverability or deploying; production rejects console.

Check Your Result

  • The email arrives with your sender address, product name, and expected language.
  • The code completes the intended sign-in or reset flow.
  • A contact-form message reaches CONTACT_FORM_TO.
  • Newsletter confirmation and signed unsubscribe links work if you use the newsletter feature.

Common Problems

ProblemFix
No message in the inboxCheck server errors, provider logs, spam, and whether MAIL_PROVIDER is still console
Provider rejects the senderVerify the sending domain and token permissions; use a sender on that domain
Links point to localhostSet the deployment's NEXT_PUBLIC_SITE_URL to your HTTPS origin and rebuild

Customize the Email

Edit shared React Email templates under packages/mail/src/templates/. Current template IDs are emailVerification, magicLink, newsletterSignup, verificationCode, verificationCodeSignin, and verificationCodeForgotPassword.

Preview them with:

pnpm --filter @musemvp/web exec email dev --dir ../../packages/mail/src/templates --port 4100

Open http://localhost:4100. Use this explicit directory: the current root mail:preview script still points at the previous template location.

For a new template, register its ID and add mail.<templateId>.subject in both locales' packages/i18n/src/translations/{en,zh}/mvp.json. Package code uses relative or package imports, not app @/ imports.

From app server code, call:

import { sendEmail } from "@/modules/mail";

await sendEmail({
  to: user.email,
  locale,
  templateId: "verificationCodeSignin",
  context: { otp, expiresIn: 300 },
});

user, locale, and otp come from your server flow. sendEmail() also supports raw subject / html / text. To add a welcome email, create a template and connect it to your chosen event; it is not an existing default template. Preview checks appearance, not delivery.

On this page