Skip to content
emailkit
Esc
navigateopen⌘Jpreview
On this page

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:

  1. Create an API key.
  2. Add and verify your sending domain.
  3. Create a webhook pointing to https://your-app.com/api/email/resend, selecting the events you need — including Email Received for inbound email.
  4. 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.

Last updated on July 24, 2026

Was this page helpful?