Resend is a developer-first email API with first-class TypeScript types, edge-runtime support, and tight integration with React Email templates. It's an excellent default for new DaloyJS projects.
Send through the resend SDK
Route handlerResend SDKResend APIWebhooks
01requestRoute handlerResend SDKresend.emails.send({ from, to, subject })uses the standard fetch API (edge-friendly)
04asyncResend APIWebhooksdelivered, bounced, complained eventsverify the signature before trusting payloads
resend.emails.send() runs on the standard fetch API, so it works on edge runtimes too. The SDK returns { data, error } rather than throwing, so handle the error branch and return data.id on success.
1. Provision
Sign up at resend.com and add a sending domain under Domains. Add the SPF, DKIM, and DMARC DNS records Resend lists, then click Verify.
Create an API key under API Keys. Use a “Sending access” key scoped to that domain.
// src/plugins/resend.tsimport { Resend } from "resend";import type { App } from "@daloyjs/core";const resend = new Resend(process.env.RESEND_API_KEY);export const resendPlugin = { name: "resend", register(app: App) { app.decorate("email", { async send({ to, subject, text, html }) { const { data, error } = await resend.emails.send({ from: process.env.RESEND_FROM!, to, subject, text, html, }); if (error) throw new Error(error.message); return { id: data?.id ?? "" }; }, }); },};declare module "@daloyjs/core" { interface AppState { email: { send(msg: { to: string; subject: string; text?: string; html?: string; }): Promise<{ id: string }>; }; }}
Resend SDK methods return { data, error } rather than throwing, handle the error branch and surface it through the DaloyJS error helpers.
5. Use it in a route
ts
import { z } from "zod";import { App, secureHeaders, rateLimit } from "@daloyjs/core";import { resendPlugin } from "./plugins/resend";const app = new App();app.use(secureHeaders());app.use(rateLimit({ windowMs: 60_000, max: 10 }));app.register(resendPlugin);app.route({ method: "POST", path: "/magic-link", operationId: "sendMagicLink", request: { body: z.object({ email: z.string().email() }), }, responses: { 202: { description: "Sent", body: z.object({ id: z.string() }) }, }, handler: async ({ body, state }) => { const link = await issueMagicLink(body.email); // your own logic const { id } = await state.email.send({ to: body.email, subject: "Your sign-in link", text: `Sign in: ${link}`, html: `<p>Sign in: <a href="${link}">${link}</a></p>`, }); return { status: 202, body: { id } }; },});async function issueMagicLink(_email: string) { return "https://acme.example.com/auth/callback?token=...";}
React Email templates
Resend reads the react field and renders it to HTML for you, so you can ship type-safe email templates as React components:
ts
pnpm add @react-email/components react react-dom
ts
// emails/welcome.tsximport { Html, Button, Heading, Text } from "@react-email/components";export default function Welcome({ name }: { name: string }) { return ( <Html> <Heading>Welcome, {name}!</Heading> <Text>Thanks for joining Acme.</Text> <Button href="https://acme.example.com/start">Get started</Button> </Html> );}// in the handlerimport Welcome from "../../emails/welcome";await resend.emails.send({ from: process.env.RESEND_FROM!, to, subject: "Welcome to Acme", react: Welcome({ name: "Devlin" }),});
Batch sending
Use resend.batch.send([...]) to enqueue up to 100 messages in a single API call, handy for fan-out notifications without queueing infrastructure.
Runtimes
The resend SDK uses the standard fetch API, so it runs on Node 18+, Bun, Deno, AWS Lambda, Vercel (Serverless and Edge), and Cloudflare Workers without adapters. Pair it with the edge adapters shipped by DaloyJS.