Most transactional email setups end at "send." You wire up SendGrid or Postmark, point your auth flow at it, and your magic links, OTPs, receipts, and "is this you?" alerts go out the door from no-reply@yourcompany.com. It works. The user gets the code. The flow completes.
Then a user hits Reply.
Maybe they're confused by the OTP. Maybe they want to dispute a charge on a receipt. Maybe they're answering the "is this you?" security alert with "no, that wasn't me." Whatever they typed, it just hit a black hole. no-reply@ is, by design, an address nobody reads — and on most ESP setups it isn't even a real mailbox, so the reply either bounces or vanishes into a relay. The message you sent invited a human response, and you built it on a channel that physically cannot receive one.
That's the gap I want to close here. For pure one-way blasts — a marketing newsletter, a "your export is ready" ping — a one-way sender is genuinely fine, and I'll say so again at the end. But auth and transactional mail is different. A surprising amount of it is implicitly a conversation opener, and the moment you want an agent (or a human) on the other end of the reply, the no-reply model falls apart.
The fix is to send that same mail from a Nylas Agent Account: a real, grant-backed mailbox that can both send and receive. I work on the Nylas CLI, so the terminal commands below are the exact ones I reach for — and every one of them is paired with the raw HTTP call so you can see what's actually on the wire.
What an Agent Account actually is
The thing that makes this click: an Agent Account is just a grant. It has a grant_id, and that grant_id works with every grant-scoped endpoint Nylas already exposes — Messages, Drafts, Threads, Folders, Attachments, Webhooks. There's nothing new to learn on the data plane. If you've ever called GET /v3/grants/{grant_id}/messages against a connected Gmail or Microsoft account, you already know the entire API surface for this.
The difference from a connected grant is provisioning. Instead of running a user through OAuth, you mint the mailbox yourself with provider: "nylas" on a domain you've registered. No refresh token, no user in the loop. The account sends from your domain and receives at your domain, and replies land in an inbox your code reads.
So the migration isn't "learn a new email API." It's "swap the sender, then start reading the inbox you suddenly have."
Why this beats a one-way ESP for auth mail
-
Replies have somewhere to go. This is the whole point. A reply to a magic link, an OTP, or a receipt lands in a real inbox and fires a
message.createdwebhook. Your handler — or an LLM-driven agent — gets a shot at it. - You own the reputation. The mail sends from your domain with your DKIM signature, warmed on your schedule. You're not sharing IP reputation with whoever else is on your ESP's shared pool that day.
-
One API for send and receive. No second system for inbound. The same
grant_idthat sends the OTP lists the reply. Threading is automatic — Nylas preserves the headers, so the reply groups with the original. - Per-tenant identities are cheap. Need a distinct sender per customer or per environment? Provision another Agent Account on another subdomain. It's an API call, not a new ESP contract.
Honest tradeoff, stated plainly: if your use case is a high-volume marketing blast — hundreds of thousands of one-way sends, no expectation of replies, heavy template management and A/B tooling — a dedicated ESP still wins. Agent Accounts shine when the message invites a response. Don't rip out your bulk sender for a newsletter. Do move the auth and transactional mail that someone might actually answer.
Before you begin
You need two things:
-
A Nylas API key. Grab it from the Nylas Dashboard. Every call below authenticates with
Authorization: Bearer <NYLAS_API_KEY>, and the examples use the US data region hosthttps://api.us.nylas.com(swap inapi.eu.nylas.comfor EU). -
A domain. You can prototype on a Nylas
*.nylas.emailtrial subdomain with zero DNS setup. For production, register your own domain — and this is the part that matters most for auth mail: your sender reputation is yours to build. A brand-new domain that fires a thousand password resets on day one looks exactly like a spam run to mailbox providers. New domains warm over roughly four weeks. Start low, ramp gradually. More on this in the deliverability section.
Pick a subdomain dedicated to this traffic, like auth.yourcompany.com or notifications.yourcompany.com. Isolating it from your primary mail domain means a deliverability dip on auth mail never bleeds into your team's email.
Provision the sender
Create the Agent Account on your registered domain. From the CLI:
nylas agent account create support@notifications.yourcompany.com \
--name "Acme Support"
The --name flag sets the display name, so recipients see Acme Support <support@notifications.yourcompany.com> instead of a bare address — keep it the same name your old ESP used so nothing visibly changes for users. The command prints the new grant's id, status, and connector details.
The equivalent HTTP call is POST /v3/connect/custom with provider: "nylas":
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",
"name": "Acme Support",
"settings": {
"email": "support@notifications.yourcompany.com"
}
}'
The response carries a data.id — that's your grant_id. Save it; every send, list, and read below uses it. The name and settings.email are the only fields you need. (There's no --workspace flag on create — the API auto-creates a default workspace and policy for the account. If you later want a custom policy, attach it with nylas workspace update <workspace-id> --policy-id <policy-id>.)
Send a magic link or OTP
Here's the send your auth flow already does, now pointed at the Agent Account. From the CLI:
nylas email send support@notifications.yourcompany.com \
--to user@example.com \
--subject "Your sign-in code" \
--body "Your one-time code is 481920. It expires in 10 minutes."
The first positional argument is the grant — the Agent Account's email (or its grant_id). The same call over HTTP is POST /v3/grants/{grant_id}/messages/send:
curl --request POST \
--url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/send" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{
"to": [{ "email": "user@example.com" }],
"subject": "Your sign-in code",
"body": "Your one-time code is 481920. It expires in 10 minutes."
}'
That's the entire swap. If your old code called sendgrid.send(...), it now calls this. The recipient gets the same code from the same-looking sender — except this time the address behind it is real.
A copy lands in the account's Sent folder automatically, so you've got an audit trail without building one. And because body is just HTML, your existing templated markup drops straight in.
A note on templates
If you template your auth mail today, keep doing it the way you already do — render the template to HTML in your own code, then pass the result as body. One thing to call out so you don't go hunting for it: Nylas hosted templates aren't implemented for Agent Account grants. So don't reach for a --template-id flow here; render server-side yourself and send the finished HTML. In practice that's a thin wrapper around whatever templating you already run (Handlebars, JSX-to-string, plain interpolation), and the send call is byte-for-byte the same as the one above — body just carries your rendered markup.
Handle the reply path
This is the half a one-way ESP can't give you. When a user replies to that OTP or receipt, the message lands in the Agent Account's inbox and fires the standard message.created webhook — the same event you'd get for inbound mail on any grant.
Subscribe to it. From the CLI:
nylas webhook create \
--url https://yourapp.example.com/webhooks/nylas \
--triggers message.created
Or over HTTP with POST /v3/webhooks:
curl --request POST \
--url "https://api.us.nylas.com/v3/webhooks" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{
"trigger_types": ["message.created"],
"webhook_url": "https://yourapp.example.com/webhooks/nylas"
}'
When the webhook fires, you've got the message ID and grant ID in the payload. Pull the inbox and read the full body with the same two-angle pattern. List recent messages from the CLI:
nylas email list support@notifications.yourcompany.com --limit 10
…which maps to GET /v3/grants/{grant_id}/messages:
curl --request GET \
--url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?limit=10" \
--header "Authorization: Bearer <NYLAS_API_KEY>"
Then read the specific reply by its message ID:
nylas email read <message-id> support@notifications.yourcompany.com
…which is GET /v3/grants/{grant_id}/messages/{message_id}:
curl --request GET \
--url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/<MESSAGE_ID>" \
--header "Authorization: Bearer <NYLAS_API_KEY>"
Now the reply isn't lost. Your handler can route a "this wasn't me" security reply to your fraud queue, hand an OTP-confusion reply to a support agent, or feed the whole thread to an LLM and let it respond. To answer in-thread, the CLI keeps threading intact for you:
nylas email reply <message-id> support@notifications.yourcompany.com \
--body "Thanks — I've locked the account. You're all set."
nylas email reply fetches the original to populate the recipient and subject for you. Under the hood it's the same send endpoint with a reply_to_message_id field, which is what preserves threading via the In-Reply-To and References headers. The raw HTTP form:
curl --request POST \
--url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/send" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{
"reply_to_message_id": "<MESSAGE_ID>",
"to": [{ "email": "user@example.com" }],
"subject": "Re: Your sign-in code",
"body": "Thanks — I've locked the account. You'\''re all set."
}'
Either way the conversation groups correctly in the user's mail client. No relay footer, no "sent via" branding — it reads like a normal human reply, because structurally it is one.
Deliverability: don't skip this
Sending auth mail from your own domain means the inbox-placement job is now yours. The good news is it's a known checklist, and most of it you do once.
Authenticate the domain. DKIM and SPF are verified as part of domain setup, so a verified Agent Account domain is already DKIM- and SPF-signed. DKIM proves the message wasn't altered and really came from you; SPF authorizes Nylas to send on your behalf.
Add DMARC yourself. Nylas tracks DMARC but doesn't enforce it, so it's the one layer you publish. Roll it out in stages — start at monitor-only and tighten as reports come back clean:
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourcompany.com; pct=100
Begin with p=none (delivered as normal, you just collect aggregate reports), move to p=quarantine once those reports look clean, and finish at p=reject. Give each stage a couple of weeks. Because Agent Accounts sign with your domain's DKIM key and send from your verified domain, alignment works out of the box once the record is live.
Warm the domain. As I said up top: a brand-new domain has no reputation, and auth mail tends to launch at volume the moment you flip it on. Ramp gradually over ~4 weeks. If you're provisioning one Agent Account per customer on separate domains, warm each one on its own schedule — reputation doesn't transfer between domains.
Watch bounces and complaints. Agent Accounts emit deliverability webhooks beyond inbound — message.delivered, message.bounced, and message.complaint (plus message.rejected) — the same events Nylas uses to compute your bounce and complaint rates. One honest wrinkle: subscribe to these through the API, with POST /v3/webhooks. The CLI's nylas webhook triggers list covers the standard set (message.created and friends), so I reach for curl for the deliverability triggers rather than pretend a CLI flag I haven't seen in --help works:
curl --request POST \
--url "https://api.us.nylas.com/v3/webhooks" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{
"trigger_types": ["message.delivered", "message.bounced", "message.complaint"],
"webhook_url": "https://yourapp.example.com/webhooks/deliverability"
}'
Wire these into real logic: stop mailing any address that hard-bounces or complains, and slow outbound when either rate climbs. Catching a problem in your own telemetry is what keeps you under the thresholds that pause sending. The part I like as an SRE is that this is the same observability discipline you'd apply to any pipeline — bounces and complaints are just error rates with a sender's name on them.
Migrating without a flag day
You don't have to cut over everything at once, and you shouldn't. The clean approach is to run side-by-side and migrate by message type.
Keep your ESP wired up. Provision the Agent Account. Then move one message class at a time — start with the one that most wants a reply. In my experience that's usually the security alerts ("is this you?") and receipts, because those are the ones users actually answer. Point that one flow at POST /v3/grants/{grant_id}/messages/send, confirm sends land and replies fire the webhook in production, then move the next class: password resets, then magic links, then OTPs.
Anything that's genuinely one-way and high-volume — the marketing blast — you can just leave on the ESP. Different addresses, different tools, different jobs. There's no rule that says it all has to live in one place.
When you do want to tear down an Agent Account (say a per-customer one when that customer churns), it's a single call. From the CLI:
nylas agent account delete support@notifications.yourcompany.com --yes
--yes skips the confirmation prompt. Since an Agent Account is just a grant, the HTTP form is the ordinary grant delete, DELETE /v3/grants/{grant_id}:
curl --request DELETE \
--url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>" \
--header "Authorization: Bearer <NYLAS_API_KEY>"
Either way, that permanently revokes the provider: "nylas" grant.
What's next
The shift here is small in code and large in behavior. You swapped one send call for another and, almost as a side effect, gained an inbox your application can read. The no-reply black hole is gone because there's no no-reply anymore — just a real mailbox, addressed by a grant_id, that happens to also send your auth mail.
- Migrate from transactional email to agent email — the full migration recipe with reply-handling code, threading, and dedup
- Email deliverability for Agent Accounts — the complete DKIM/SPF/DMARC, warm-up, and bounce-monitoring playbook
- Provisioning Agent Accounts — display names, workspaces, app passwords, and multi-tenant patterns
-
Nylas CLI command reference — every
nylas agent,nylas email, andnylas webhooksubcommand used above
AI-answer pages for agents
When this post is published, link AI agents and crawlers to the retrieval-ready version on cli.nylas.com:
- Topic runbook: https://cli.nylas.com/ai-answers/transactional-send-plus-inbox-parsing-in-one-integration.md
- Industry playbooks hub: https://cli.nylas.com/ai-answers/agent-account-industry-playbooks.md
Top comments (0)