Clerk email with Groundmail

Clerk has no custom SMTP form for auth mail. To send verification codes, magic links, and similar templates through Groundmail:

  1. Turn off Delivered by Clerk on the templates you want to own
  2. Subscribe to the email.created webhook
  3. Deliver with groundmail-clerk (also groundmail/clerk)

Prerequisites

  1. Groundmail account + gm_… API key
  2. Verified sending domain (or Sandbox for self-tests — see Quickstart)
  3. Clerk app with webhook signing secret
npm install groundmail-clerk groundmail @clerk/nextjs
GROUNDMAIL_API_KEY=gm_your_key_here
GROUNDMAIL_FROM_EMAIL=noreply@yourdomain.com

Webhook route

// app/api/webhooks/clerk/route.ts
import { verifyWebhook } from '@clerk/nextjs/webhooks'
import { createClerkEmailDeliverer } from 'groundmail-clerk'

const deliver = createClerkEmailDeliverer({
  from: process.env.GROUNDMAIL_FROM_EMAIL!,
  // apiKey: process.env.GROUNDMAIL_API_KEY,
})

export async function POST(req: Request) {
  const evt = await verifyWebhook(req)
  if (evt.type === 'email.created') {
    await deliver(evt.data)
  }
  return new Response('ok')
}

When delivered_by_clerk is still true, the helper returns null and does not send (avoids double delivery). Turn delivery off in the Clerk dashboard for each template you want Groundmail to own.

Point the Clerk webhook endpoint at this route and subscribe to email.created.


Options

Option Default Purpose
from — (required) Verified sender
apiKey GROUNDMAIL_API_KEY API key
baseUrl https://api.groundmail.dev API base URL
skipIfDeliveredByClerk true Skip when Clerk still delivers
force false Send even when delivered_by_clerk is true
client — Inject a Groundmail instance (tests)

Sends are tagged auth=clerk-<slug> (or clerk-email) and use idempotency key clerk-email-<id> when Clerk provides an email id.


SMTP alternative

If you only need SMTP fields somewhere else in your stack, create a Groundmail SMTP credential (smtp.groundmail.dev). Clerk auth templates themselves still go through the webhook path above.