# Email System
Transactional email templates for Next.js 14 via Resend — welcome, verify, password reset, purchase receipt, refund, team invite, affiliate payout, generic notification, and newsletter blast, all sharing a single branded HTML layout.
## What's included
**Transactional senders**
- `sendWelcomeEmail(to, name)` — account creation confirmation with CTA to browse blocks
- `sendEmailVerification(to, name, token)` — verification link with 24-hour expiry note; includes plain URL fallback
- `sendPasswordResetEmail(to, name, token)` — reset link with 1-hour expiry note; includes plain URL fallback
- `sendPurchaseReceipt(to, opts)` — receipt with line item table, GitHub repo link, and 30-day guarantee notice; accepts `PurchaseReceiptOpts`
- `sendRefundEmail(to, opts)` — refund confirmation with amount and PayPal timeline note
- `sendTeamInvite(to, opts)` — workspace invitation with role, 7-day expiry, and plain URL fallback
- `sendAffiliatePayoutEmail(to, opts)` — payout notification with amount and destination PayPal email
- `sendNotificationEmail(to, opts)` — generic template accepting `title`, `body`, optional `ctaText`/`ctaUrl`; use for one-offs
**Batch & newsletter**
- `sendBatch(emails)` — chunks up to 100 emails per Resend batch call; handles arrays of any size
- `sendNewsletter(opts)` — builds per-recipient HTML (individual unsubscribe tokens) and calls `sendBatch`
**Utilities**
- `generateUnsubToken(email)` — HMAC-SHA256 token derived from email + `NEXTAUTH_SECRET`; deterministic so you can verify it server-side without storing it
- `isValidEmail(email)` — regex + length check (≤ 320 chars)
- `sendTestEmail(to)` — sends a minimal delivery test; use in dev to confirm Resend is wired
**Internals (not exported but worth knowing)**
- `layout(opts, body)` — shared HTML shell with logo, card, footer, and unsubscribe link; inline CSS only for email client compatibility
- `escHtml(s)` — HTML-escapes all user-controlled strings before injection
- `injectUnsub(html, email)` — replaces `{{UNSUB_TOKEN}}` placeholder with the generated token
## Setup
### 1. Install dependencies
```bash
npm install resend
```
### 2. Environment variables
```
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxx
FROM_EMAIL=noreply@yourdomain.com
NEXT_PUBLIC_APP_URL=https://yourdomain.com
NEXTAUTH_SECRET=your NextAuth secret # used to sign unsubscribe tokens
```
### 3. Add to your project
```ts
// Server-only — never import in client components
import { sendWelcomeEmail } from '@/blocks/email'
```
You'll also need to handle the unsubscribe route at `GET /unsubscribe?token=...` — verify the token with `generateUnsubToken(email)` and mark the user as unsubscribed in your database.
## Usage examples
```ts
// After registration
import { sendWelcomeEmail, sendEmailVerification } from '@/blocks/email'
import { registerUser } from '@/blocks/auth'
const { user, verifyToken } = await registerUser({ name, email, password })
await sendWelcomeEmail(email, name)
await sendEmailVerification(email, name, verifyToken)
```
```ts
// After a confirmed PayPal capture
import { sendPurchaseReceipt } from '@/blocks/email'
await sendPurchaseReceipt(user.email, {
name: user.name,
blockName: 'Auth System',
blockId: 'auth',
amount: 19,
currency: 'USD',
orderId: paypalOrderId,
githubRepo: 'marrowstack-auth',
githubOwner: 'marrowstack',
})
```
```ts
// Newsletter to all pro users (from admin route)
import { getAllUserEmails } from '@/blocks/admin'
import { sendNewsletter } from '@/blocks/email'
const emails = await getAllUserEmails({ proOnly: true })
await sendNewsletter({
subject: 'New block drop: Billing System',
title: 'New block just dropped 🎉',
body: 'The Billing & Subscriptions block is now live...',
ctaText: 'View Block',
ctaUrl: 'https://marrowstack.dev/blocks/billing',
recipients: emails.map(e => ({ email: e, name: '' })),
})
```
## Notes
- Every user-supplied string passes through `escHtml` before being interpolated into templates — do not skip this if you add new templates
- `sendNewsletter` generates a unique unsubscribe token per recipient, so each email has a different footer link; `sendBatch` just passes HTML through as-is, so if you use it directly you're responsible for injecting tokens
- `BRAND`, `ACCENT`, and the hardcoded block count (`17`) in `sendWelcomeEmail` are module-level constants — update them before shipping; they're not driven by env vars
- Resend's free tier allows 3,000 emails/month and 100/day — `sendBatch` will silently succeed per chunk but you can hit the daily cap mid-blast; add rate limiting or schedule large newsletters in chunks if you're on the free plan