Resend
Set up Resend for sending, receiving, delivery events, and domains.
Resend is the quickest provider to get running: an API key to send, plus a webhook secret when you want to receive email and delivery events.
1. Create the Resend credentials
In Resend:
- Create an API key.
- Add and verify your sending domain.
- Create a webhook pointing to
https://your-app.com/api/email/resend, selecting the events you need — including Email Received for inbound email. - Copy the webhook signing secret (
whsec_...).
RESEND_API_KEY="re_..."
RESEND_WEBHOOK_SECRET="whsec_..."
PUBLIC_BASE_URL="https://your-app.com"
2. Configure emailkit
import { EmailKit, ResendDriver } from "emailkit";
export const emailkit = EmailKit({
emailDrivers: [
ResendDriver({
id: "resend",
apiKey: process.env.RESEND_API_KEY!,
webhookSecret: process.env.RESEND_WEBHOOK_SECRET!,
}),
],
publicRoutes: {
baseUrl: process.env.PUBLIC_BASE_URL!,
},
hooks: {
email: {
onInbound: async (email) => {
await prisma.inboundEmail.upsert({
where: { messageId: email.messageId },
create: {
messageId: email.messageId,
from: email.from.email,
subject: email.subject,
text: email.text,
},
update: {},
});
},
onBounced: async (event) => {
await prisma.suppression.upsert({
where: { email: event.recipient },
create: { email: event.recipient },
update: {},
});
},
},
},
});
The database is yours — emailkit stays stateless. The upserts matter: providers may retry events, and an upsert by messageId makes that harmless.
3. Add the public route
import { createNextEmailKitHandler } from "emailkit/nextjs";
import { emailkit } from "@/src/emailkit";
export const { GET, POST } = createNextEmailKitHandler(emailkit, {
emailDriver: async (_request, context) =>
(await context.params).emailDriver,
});
Resend signs the exact request body; the adapter preserves it and emailkit rejects anything that doesn’t verify. Just don’t call request.json() before the adapter.
4. Send a test email
const result = await emailkit.sendEmail({
from: { email: "hello@example.com", name: "Acme" },
to: { email: "ada@example.com" },
subject: "emailkit is connected",
text: "This message was sent through Resend.",
idempotencyKey: "resend-setup-test-1",
});
Use a stable idempotencyKey when a retry must not create a second email. Then send a message to your Resend receiving domain and watch onInbound fire.
Recover missed inbound email
If your route was down, replay through the same hooks:
await emailkit.sync({
emailDriver: "resend",
since: outageStartedAt,
});
Resend can replay inbound email; delivery, open, and click history is not exposed by its API, so keep those handlers reliable.
Extras
Resend also supports templates, scheduling (sendAt), tags, metadata, domain management, and provider-only endpoints through providerFetch() — all visible in TypeScript once the driver is configured.
