# How to send Clerk emails with a Nylas Agent Account

Source: https://developer.nylas.com/docs/cookbook/agent-accounts/send-clerk-emails/

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](https://clerk.com/docs/guides/customizing-clerk/email-sms-templates) 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.

> **Info:** 
> **Before you begin.** You need a domain registered with Nylas. A `*.nylas.email` trial subdomain works for prototyping (no DNS setup); for production, register your own domain and add the MX and TXT records. Both paths are covered in [Setup domains](/docs/v3/agent-accounts/dns-provider-setup/).

## 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:

```bash
nylas agent account create noreply@auth.yourcompany.com
```

Or via the API:

```bash
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": "noreply@auth.yourcompany.com"
    }
  }'
```

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

In the [Clerk Dashboard](https://dashboard.clerk.com/~/customization/email), 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](https://clerk.com/docs/guides/customizing-clerk/email-sms-templates) page lists the full inventory and their slugs).

For each template you want Nylas to deliver:

1. Open the template.
2. Toggle **Delivered by Clerk** to **off**.
3. 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

Add a webhook endpoint on the [Webhooks page](https://dashboard.clerk.com/~/webhooks) 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()`](https://clerk.com/docs/reference/backend/verify-webhook) 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:

```js


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

`deliverViaNylas` is where Clerk's webhook becomes a Nylas send. Use [`POST /v3/grants/{grant_id}/messages/send`](/docs/reference/api/messages/send-message/) with the rendered body from Clerk:

```js
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 `deliverViaNylas` succeeds 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 `200` to 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 `200` and log. A 5xx from Nylas is worth retrying — return non-2xx so Clerk re-fires.

## 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`](/docs/reference/api/messages/get-messages/) 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

- **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.created` webhook. Wire that up via [Handle replies in an agent loop](/docs/cookbook/agent-accounts/handle-replies/) 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_password` on the grant and point your transport at `mail.us.nylas.email:587`. Same Agent Account either way.

## What's next

- [Clerk: Email and SMS templates](https://clerk.com/docs/guides/customizing-clerk/email-sms-templates) — the `Delivered by Clerk` toggle and template reference
- [Clerk: `verifyWebhook()` reference](https://clerk.com/docs/reference/backend/verify-webhook) — signature verification helper
- [Mail client access (IMAP & SMTP)](/docs/v3/agent-accounts/mail-clients/) for the reference on app passwords, ports, and protocol behavior
- [Handle replies in an agent loop](/docs/cookbook/agent-accounts/handle-replies/) to process inbound replies from Clerk auth flows
- [Send Auth0 emails with a Nylas Agent Account](/docs/cookbook/agent-accounts/send-auth0-emails/) for the SMTP-based equivalent on Auth0
- [Send Supabase emails with a Nylas Agent Account](/docs/cookbook/agent-accounts/send-supabase-emails/) for the SMTP-based equivalent on Supabase