Clerk doesn’t expose a raw SMTP form the way Supabase or Auth0 do — there’s no host/port/username/password screen. Its approach is different on purpose. Each email template has a Delivered by Clerk toggle, and when you flip it off, Clerk renders the email as usual but hands it to your application through the emails.created webhook instead of putting it on the wire. From there, you decide where it goes.
That webhook-as-a-handoff model pairs cleanly with a Nylas Agent Account. Clerk continues to own the auth flow, the templates, and the OTP/magic-link generation; Nylas owns the sending mailbox and — critically — the inbox that catches replies when a user hits Reply on a verification email.
The handoff runs through two webhooks: Clerk fires emails.created with the rendered email, your handler relays it to Nylas via POST /v3/grants/{grant_id}/messages/send, and any reply that comes back triggers a Nylas message.created webhook for your agent (or a human) to pick up. Clerk keeps the templates and the auth flow; Nylas owns the sending mailbox and the inbox. This guide stitches the two together end to end.
Provision the Agent Account
Section titled “Provision the Agent Account”Unlike SMTP integrations, the webhook path doesn’t need an app_password — you’ll call the Nylas REST API directly from your handler using your standard Nylas API key, with the Agent Account’s grant_id as the path parameter. So creation is the lighter form:
Or via the API:
curl --request POST \ --url "https://api.us.nylas.com/v3/connect/custom" \ --header "Authorization: Bearer <NYLAS_API_KEY>" \ --header "Content-Type: application/json" \ --data '{ "provider": "nylas", "settings": { "email": "[email protected]" } }'Keep the returned grant_id somewhere your webhook handler can read it (env var, secret manager — same place your Nylas API key lives).
If you also want desktop mail clients or a future SMTP path to work against the same address, you can add an app_password at creation time or later. It’s optional for this recipe.
Turn off “Delivered by Clerk” for the templates you’re moving
Section titled “Turn off “Delivered by Clerk” for the templates you’re moving”In the Clerk Dashboard, go to Customization → Emails. You’ll see Clerk’s built-in templates — verification code, magic link, and invitation are the documented set (Clerk’s Email and SMS templates page lists the full inventory and their slugs).
For each template you want Nylas to deliver:
- Open the template.
- Toggle Delivered by Clerk to off.
- Save.
You don’t have to flip them all at once — Clerk treats the toggle per-template, so you can move verification first, confirm it’s solid in production, and migrate the rest after. Anything you leave on continues to ship through Clerk’s default ESP unchanged.
While you’re in the Emails section, set the From local part to match your Agent Account (the part before the @). Clerk lets you change the local part of the address but the full sender is composed as <local part>@<your-clerk-mail-domain>, where the domain is configured at the instance level. Make sure that domain matches the domain you provisioned with Nylas, otherwise the address Clerk shows in templates and the address Nylas actually sends from will drift apart.
Wire up the emails.created endpoint
Section titled “Wire up the emails.created endpoint”Add a webhook endpoint on the Webhooks page in the Clerk Dashboard, point it at your /api/webhooks/clerk route (or wherever you’re handling Clerk events), and subscribe to the emails.created event. Copy the signing secret into CLERK_WEBHOOK_SIGNING_SECRET.
Clerk recommends verifyWebhook() from @clerk/backend for signature verification — it picks up the signing secret from the env var automatically and handles the Svix headers for you. A minimal Web-standard handler:
import { verifyWebhook } from "@clerk/backend/webhooks";
export async function POST(request) { let event; try { event = await verifyWebhook(request); } catch { return new Response("bad signature", { status: 400 }); }
if (event.type !== "emails.created") { return new Response("ignored", { status: 200 }); }
await deliverViaNylas(event.data); return new Response("ok", { status: 200 });}The first time you save a real delivery, log the full event.data object once and inspect it. Clerk’s payload includes the rendered email plus metadata — typically the recipient address, the rendered HTML body, a plain-text fallback, the subject, the template slug, and a nested data field with template variables like the OTP code or magic link URL. The exact field names are stable per template, but logging your first webhook and comparing it to the Clerk Dashboard’s Send example payload is the fastest way to confirm the shape for your tenant before you depend on it.
Hand the email off to Nylas
Section titled “Hand the email off to Nylas”deliverViaNylas is where Clerk’s webhook becomes a Nylas send. Use POST /v3/grants/{grant_id}/messages/send with the rendered body from Clerk:
async function deliverViaNylas(email) { const res = await fetch( `https://api.us.nylas.com/v3/grants/${process.env.NYLAS_GRANT_ID}/messages/send`, { method: "POST", headers: { Authorization: `Bearer ${process.env.NYLAS_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ subject: email.subject, body: email.body, // Clerk's rendered HTML to: [{ email: email.to_email_address }], }), } );
if (!res.ok) { throw new Error(`Nylas send failed: ${res.status} ${await res.text()}`); }}A few things this handler is not doing that you should consider before production:
- Idempotency. Clerk retries on non-2xx responses and on timeouts. If
deliverViaNylassucceeds but the response back to Clerk doesn’t make it, Clerk will retry and you’ll double-send. Use the Svix message ID from the request headers as a deduplication key in a short-lived cache or DB column, and skip sends you’ve already acknowledged. - Async return. Return
200to Clerk fast, and push the Nylas send onto a queue if it might be slow. A timeout looks like a failure and triggers a retry. - Error handling. A 4xx from Nylas (bad recipient, etc.) is permanent — don’t make Clerk retry it. Acknowledge with
200and log. A 5xx from Nylas is worth retrying — return non-2xx so Clerk re-fires.
Test the round trip
Section titled “Test the round trip”Create a test user in Clerk so the verification email fires. The message should arrive from your Agent Account, and a copy should appear in the account’s Sent folder — confirm with GET /v3/grants/{grant_id}/messages?in=sent or by checking the mailbox from a regular mail client.
If nothing arrives:
- The Clerk Dashboard’s Message Attempts view (under your webhook endpoint) shows each delivery, the response code your endpoint returned, and the raw payload. That’s where signature mismatches and timeouts surface first.
- The Nylas Dashboard’s grant logs show whether the send hit Nylas at all, and what the SMTP-layer response was.
Things worth knowing
Section titled “Things worth knowing”- The webhook isn’t a transactional API. Treat it like any Svix-style event: verify the signature, dedupe by the Svix message ID, return 2xx quickly, retry on 5xx, swallow on 4xx. The Clerk Dashboard’s Message Attempts view is where you’ll diagnose failed deliveries.
- Reply handling is the real win. A user replying to a Clerk magic-link email used to go nowhere — Clerk’s default sender is unattended. Through Nylas, that reply lands in the Agent Account’s inbox and fires a
message.createdwebhook. Wire that up via Handle replies in an agent loop to give the agent (or a human) a chance to actually respond. - You can migrate one template at a time. “Delivered by Clerk” is a per-template toggle, so you don’t need a flag day. Verification first, password reset next, leave invitations on Clerk until you’re confident.
- Keep the FROM domain consistent. Clerk shows the sender in template previews; Nylas enforces it on send. If those drift apart (e.g., you reconfigured the Clerk mail domain but didn’t touch Nylas), users see one address and the actual envelope shows another, which hurts deliverability.
- Watch the send cap. Agent Accounts default to 100 messages per day. For a low-traffic Clerk tenant that’s fine; for a B2C app with constant signups, request a raise or shard across multiple Agent Accounts and pick one per recipient.
- You can drop SMTP in later. This recipe uses the Nylas REST API because it’s the most direct path from a webhook handler. If you’d rather have your handler talk SMTP — for example, to reuse an existing nodemailer transport — set an
app_passwordon the grant and point your transport atmail.us.nylas.email:587. Same Agent Account either way.
What’s next
Section titled “What’s next”- Clerk: Email and SMS templates — the
Delivered by Clerktoggle and template reference - Clerk:
verifyWebhook()reference — signature verification helper - Mail client access (IMAP & SMTP) for the reference on app passwords, ports, and protocol behavior
- Handle replies in an agent loop to process inbound replies from Clerk auth flows
- Send Auth0 emails with a Nylas Agent Account for the SMTP-based equivalent on Auth0
- Send Supabase emails with a Nylas Agent Account for the SMTP-based equivalent on Supabase