resend-email-nextjs
Skill0 starsChanged 3 months agoMITView on GitHub
Sends transactional email from Next.js via Resend: API routes, env config, HTML templates, attachments, feedback forms, and sandbox vs production setup. Use when adding email sending, contact forms, PDF attachments, Resend, or transactional notifications in a Next.js app.
- Reads credentials
- Installs packages
- Sends data out
In this repository23 other files it gives its agents.
- client-document-exportSkill
- cloudflare-turnstile-nextjsSkill
- ga4-analytics-nextjsSkill
- git-repo-privacy-auditSkill
- hardened-contact-form-nextjsSkill
- html-semantic-markupSkill
- multi-step-wizard-flowSkill
- nextjs-api-routesSkill
- nextjs-app-routerSkill
- nextjs-seo-metadataSkill
- nextjs-ui-foundationSkill
- next-themes-dark-modeSkill
- playwright-e2e-testingSkill
- react-components-patternsSkill
- react-hooks-patternsSkill
- recharts-dashboardSkill
- server-pdf-playwrightSkill
- shadcn-ui-setupSkill
- specialized-form-inputsSkill
- tailwind-css-v4Skill
- vitest-unit-testingSkill
- web-accessibility-wcagSkill
- zustand-persist-stateSkill
The file
We split this skill into the rules it gives an agent
7 rules. About 0 are common, 3 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 27
Document all vars in .env.example with comments, no real keys.
- Very specific: line 71
Always escapeHtml() user-provided strings in HTML templates.
- Distinctive: line 120
Prefer server-side PDF generation for large payloads and security.
- Distinctive: line 132
Add domain in Resend dashboard
- Very specific: line 134
Update EMAIL_FROM to verified sender
- Very specific: line 139
Never expose RESEND_API_KEY to client
- Distinctive: line 153
Keep templates inline initially.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Resend Email in Next.js
Setup
npm install resendEnvironment
# .env.local (never commit)
RESEND_API_KEY=re_xxxx
EMAIL_FROM="Your App <onboarding@resend.dev>"
FEEDBACK_TO_EMAIL=you@example.comDocument all vars in .env.example with comments, no real keys.
Config module (lib/email-config.ts)
export function getEmailConfig() {
return {
resendApiKey: process.env.RESEND_API_KEY,
fromEmail: process.env.EMAIL_FROM ?? "App <onboarding@resend.dev>",
feedbackToEmail: process.env.FEEDBACK_TO_EMAIL,
};
}
export function isEmailConfigured(): boolean {
return Boolean(process.env.RESEND_API_KEY);
}Lazy-init client inside send functions — don't construct at module load (build-time safety).
Send helper
import { Resend } from "resend";
function getResend() {
const { resendApiKey } = getEmailConfig();
if (!resendApiKey) throw new Error("RESEND_API_KEY not configured");
return new Resend(resendApiKey);
}
export async function sendEmail(opts: {
to: string | string[];
subject: string;
html: string;
replyTo?: string;
attachments?: { filename: string; content: string }[]; // base64
}) {
const { fromEmail } = getEmailConfig();
const { error } = await getResend().emails.send({ from: fromEmail, ...opts });
if (error) throw new Error(error.message);
}Always escapeHtml() user-provided strings in HTML templates.
API route pattern
// app/api/feedback/route.ts
export async function POST(req: Request) {
if (!isEmailConfigured()) {
return Response.json({ error: "Email not configured" }, { status: 503 });
}
const body = await req.json();
const parsed = feedbackSchema.safeParse(body);
if (!parsed.success) {
return Response.json({ error: parsed.error.flatten() }, { status: 400 });
}
try {
await sendFeedbackEmail(parsed.data);
return Response.json({ ok: true });
} catch (e) {
return Response.json({ error: "Failed to send" }, { status: 500 });
}
}Validate with Zod server-side. Rate-limit public routes — see nextjs-api-routes.
Client form
const res = await fetch("/api/feedback", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, email, message }),
});
if (!res.ok) throw new Error((await res.json()).error);Show loading state; track success/error analytics events.
Attachments (PDF export email)
Generate PDF server-side or accept base64 from client:
attachments: [{ filename: "report.pdf", content: pdfBase64 }],Prefer server-side PDF generation for large payloads and security.
Sandbox vs production
| Mode | From address | To address |
| Sandbox | onboarding@resend.dev | Only the email on your Resend account |
| Production | Verified domain (notifications@yourdomain.com) | Any recipient |
Gmail +alias tricks do not work in sandbox — use exact account email for testing.
Production steps:
- Add domain in Resend dashboard
- Publish DNS records (SPF, DKIM)
- Update
EMAIL_FROMto verified sender
Security
- Rate-limit public feedback endpoints (middleware or edge)
- Never expose
RESEND_API_KEYto client - Sanitize/escape all user HTML
- Validate email format server-side
- Cap attachment size
HTML template baseline
<h2>Subject context</h2>
<p><strong>From:</strong> {{name}} <{{email}}></p>
<p style="white-space:pre-wrap">{{message}}</p>
<p style="color:#64748b;font-size:12px;margin-top:24px">Sent via Your App</p>Keep templates inline initially; extract to React Email only when complexity grows.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

