A shared inbox where the agent drafts and humans approve from Outlook is a powerful hybrid — and it's the part of "AI email" that most demos quietly skip.
Most "AI email" demos point a model at a human's personal inbox: connect a Gmail grant, summarize threads, maybe auto-draft a reply that nobody actually trusts enough to send. That's fine for a personal assistant. It falls apart the moment you want the agent to be a participant on a real team mailbox — support@yourcompany.com — where five humans and one agent all need to see the same messages, the same folders, and the same draft sitting in the queue.
The naive fix is to give the agent its own service and bolt a UI on top. Now you're building a mail client. You've got a database of "agent state," a sync loop, a permissions layer, and a frontend nobody asked for, all to reimplement what Outlook already does well.
There's a better shape. A Nylas Agent Account is a real mailbox you can drive two ways at once: the agent works it over the API, and your humans work it from their normal mail client over IMAP/SMTP. Both surfaces read and write the same storage. A draft the agent creates via the API shows up in the Drafts folder in Apple Mail. A message a human drags into "Needs review" in Outlook shows up in the API's folders field within seconds. That bidirectional sync is the whole trick, and it's what makes genuine human+agent collaboration possible instead of two systems fighting over one inbox.
I work on the Nylas CLI, so the terminal commands below are the exact ones I reach for when I'm wiring this up. Every step shows the raw curl call and the nylas equivalent, because you'll want the API in your service and the CLI on your laptop.
What you actually get
An Agent Account is just a grant — the same grant_id abstraction you already use for every other Nylas mailbox. Nothing new to learn on the data plane:
- Agent side (API): Messages, Drafts, Folders, Threads, Attachments, Webhooks — every grant-scoped endpoint works exactly as it does for a Gmail or Microsoft grant.
- Human side (IMAP/SMTP): Outlook, Apple Mail, Thunderbird connect with the account email and an app password. People work the mailbox the way they always have.
- Shared state: flags, folder moves, drafts, and sends propagate across both surfaces and fire the same webhooks, no matter which side initiated them.
The model is: the agent triages and drafts, a human reviews and approves from the comfort of their mail client, and the queue between them is just a folder both sides can see.
Why this beats building your own review UI
- You don't write a mail client. Outlook is the review UI. Humans approve drafts where they already live.
- The agent doesn't poll a side-channel. It gets
message.createdwebhooks for inbound mail and reads/writes the real mailbox. - There's one source of truth. No reconciliation between "agent's view" and "human's view" — it's literally one mailbox.
- It degrades gracefully. If your agent service is down, the humans still have a fully working mailbox. If a human is on vacation, the agent still triages.
One honest caveat up front, because it matters for how you scope this: this is not a multi-user ACL system. There's one app password for the mailbox, and everyone who connects a client shares it. You can't grant Alice read-only and Bob send. If you need per-person permissions, that lives in your application layer in front of the API, not in the shared inbox. I'll come back to the security tradeoff at the end.
Before you begin
You need:
- A Nylas application and API key.
- A registered sending domain — either your own custom domain or a Nylas
*.nylas.emailtrial subdomain. New domains warm over roughly four weeks, so don't provision one the morning of a launch. - An email address on that domain for the inbox, e.g.
support@yourcompany.com.
The API base host in these examples is https://api.us.nylas.com (use the EU host if your app is in the EU region), and every request authenticates with Authorization: Bearer <NYLAS_API_KEY>.
If this is your first Agent Account, read the Agent Accounts quickstart first. The walkthrough below assumes you've at least skimmed it.
Provision the shared mailbox
An Agent Account is created with POST /v3/connect/custom, "provider": "nylas", and the email on your registered domain. No OAuth, no refresh token — it's a Nylas-hosted mailbox. The optional top-level name sets the display name humans see in the From field.
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": "Support Team",
"settings": {
"email": "support@yourcompany.com",
"app_password": "Sh4redInb0xReview2024!"
}
}'
Notice I'm setting app_password right in the create call. That's the credential your humans use to connect their mail clients (more on it next). The CLI does the same thing in one line:
nylas agent account create support@yourcompany.com \
--name "Support Team" \
--app-password "Sh4redInb0xReview2024!"
The response gives you a grant_id. That's the only identifier the agent side needs from here on.
Workspaces, briefly. When you create the account, the API auto-creates a default workspace and policy for it. If you run several agent inboxes — say a sales-outreach group and a support-triage group with different send limits — you can drop this account into a specific workspace at creation by passing a workspace_id in the POST /v3/connect/custom body. The CLI create command has no --workspace flag; to attach a custom policy after the fact you use nylas workspace update <workspace-id> --policy-id <policy-id>. For a single shared inbox the default workspace is fine — you can ignore this entirely.
Set the app password so humans can connect
The app password is what turns an API-only mailbox into a human+agent one. Until you set it, Nylas rejects every IMAP LOGIN and SMTP AUTH attempt. I set it at creation above, but you can also set or rotate it on an existing account.
Over the API, a PATCH to the grant replaces the whole settings object, so send the email alongside the new password:
curl --request PATCH \
--url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{
"settings": {
"email": "support@yourcompany.com",
"app_password": "Sh4redInb0xReview2024!"
}
}'
The CLI has a dedicated command for it:
nylas agent account update support@yourcompany.com \
--app-password "Sh4redInb0xReview2024!"
The password must be 18–40 characters, printable ASCII, with at least one uppercase, one lowercase, and one digit. Nylas stores it as a bcrypt hash — you can't read it back, only reset it. Rotating it disconnects any live mail client, and each one reconnects after the user types the new value.
Now hand your team these connection settings. They're exact, straight from the Nylas mail-client docs:
| Setting | Value |
|---|---|
| IMAP server |
mail.us.nylas.email (US) · mail.eu.nylas.email (EU) |
| IMAP port |
993 (implicit TLS) |
| SMTP server |
mail.us.nylas.email (US) · mail.eu.nylas.email (EU) |
| SMTP port |
465 (implicit TLS) or 587 (STARTTLS) |
| Username | The account email, e.g. support@yourcompany.com
|
| Password | The app password you just set |
Most clients auto-detect TLS. If yours asks, pick SSL/TLS for 993 and 465, STARTTLS for 587. With IMAP IDLE, clients see new mail pushed in without polling — so when the agent moves a message, the human's Outlook updates on its own.
The key insight: one mailbox, two surfaces
This is the part worth internalizing, because everything else builds on it. IMAP and the API are not two copies that sync — they're two doors into the same room. Every user-visible action flows through the same storage and fires the same webhook, regardless of which door it came through:
- The agent marks a message read via the API → the human's client sees it as read.
- A human stars a message in Apple Mail → the API's
starredfield flips totrueand amessage.updatedwebhook fires. - A human drags a message to a folder in Outlook → the API's
foldersarray updates. - The agent creates a draft via the API → it appears in the Drafts folder in every connected client.
Propagation is seconds, not minutes. That tight loop is exactly what you need for "agent drafts, human approves" to feel like one workflow instead of two disconnected ones.
Build the review queue out of folders
The cleanest way to coordinate agent and humans is a shared work queue, and the simplest queue is a folder. Create a "Needs review" folder once; both sides can see it.
Over the API:
curl --request POST \
--url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/folders" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{ "name": "Needs review" }'
Or from the terminal:
nylas email folders create "Needs review" <NYLAS_GRANT_ID>
Both return the folder's ID. Stash it — the agent needs it to route messages, and it shows up as a mailbox in everyone's mail client automatically.
When inbound mail arrives, the agent decides what it can't handle alone and moves those messages into the queue. A message's folder membership lives in its folders array, so you update the message with PUT /v3/grants/{grant_id}/messages/{message_id}:
curl --request PUT \
--url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/<MESSAGE_ID>" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{ "folders": ["<NEEDS_REVIEW_FOLDER_ID>"] }'
From the CLI it's one command:
nylas email move <MESSAGE_ID> --folder <NEEDS_REVIEW_FOLDER_ID> <NYLAS_GRANT_ID>
The instant that runs, the message disappears from INBOX and shows up under "Needs review" in your team's Outlook. The folder is the handoff.
The agent drafts; a human sends
Here's the core workflow. The agent never sends on its own — it writes a draft and leaves it for a human. Drafting is a first-class endpoint: POST /v3/grants/{grant_id}/drafts.
curl --request POST \
--url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/drafts" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{
"to": [{ "email": "customer@example.com" }],
"subject": "Re: Trouble resetting my password",
"body": "Hi there — I can see your account is locked after several failed attempts. I have unlocked it; you should be able to reset now. Let me know if it sticks."
}'
The CLI version, the one I use when I'm testing a prompt by hand:
nylas email drafts create <NYLAS_GRANT_ID> \
--to customer@example.com \
--subject "Re: Trouble resetting my password" \
--body "Hi there — I can see your account is locked after several failed attempts. I have unlocked it; you should be able to reset now. Let me know if it sticks."
That draft now sits in the Drafts folder. Because IMAP and the API share state, your reviewer opens Outlook, sees the agent's draft, edits a sentence if they want, and hits send — entirely inside their mail client. No bespoke approval UI, no extra service. The human's mail client is the approval step.
If you'd rather the agent send on approval — say a reviewer clicks "approve" in your own tool and your backend sends the agent's draft — you send the draft by ID with POST /v3/grants/{grant_id}/drafts/{draft_id} (the send-draft endpoint), or from the CLI:
nylas email drafts send <DRAFT_ID> <NYLAS_GRANT_ID>
Both paths are valid; pick based on where you want the human's "yes" to live. For replies that need to stay threaded, the CLI has a shortcut that fetches the original to preserve the subject and threading headers:
nylas email reply <MESSAGE_ID> --body "Thanks for the patience — all set now." <NYLAS_GRANT_ID>
Read what came in
The agent reads the mailbox like any grant. List recent messages with 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>"
Or:
nylas email list <NYLAS_GRANT_ID> --limit 10
To pull one message's full content, including its body, fetch it by ID — GET /v3/grants/{grant_id}/messages/{message_id} — or nylas email read <MESSAGE_ID> <NYLAS_GRANT_ID>. That by-id fetch matters for webhooks, which is the next piece.
Wire up webhooks so the agent reacts in real time
You don't want the agent polling. Inbound mail fires the standard message.created event. One important detail: webhooks are application-scoped, not grant-scoped. You subscribe once at the app level with POST /v3/webhooks, and events for every grant in your app land at that one endpoint, each payload carrying the grant_id so you can route it to the right inbox.
A few things to get right:
-
Don't rely on the webhook payload for the body. Fetch the full message with
GET /v3/grants/{grant_id}/messages/{message_id}when you need the content, and branch onmessage.created.truncated— Nylas uses that event type for very large messages, at which point you re-fetch by ID anyway. Treating the by-id fetch as your source of truth keeps you correct in both cases. -
Dedupe on the top-level notification
id. Delivery is at-least-once (up to three attempts per event), and that outeridis constant across all retries of a single event — it's your dedup key. You can additionally guard on the inner message id (data.object.id) so you never act twice on the same message. -
Verify the signature. Each webhook carries an
X-Nylas-Signatureheader: a hex HMAC-SHA256 of the raw request body using your webhook secret. Compare it with a constant-time compare like Node'scrypto.timingSafeEqual, but check that both buffers are the same length first — it throws on a length mismatch. Locally,nylas webhook verifyruns the same check so you can sanity-test before deploying.
With that in place, the loop closes: mail arrives → message.created → agent fetches the body → agent triages, drafts, and moves to "Needs review" → a human approves from Outlook. Humans and agent, same mailbox, in real time.
Guardrails and honest tradeoffs
A few things I'd want a teammate to know before they ship this:
- One password, one trust boundary. Everyone with the app password has full read/write to the mailbox. There's no per-user ACL. Treat the app password like a shared production credential: store it in a secrets manager, rotate it on personnel changes, and remember that rotating it kicks every connected client. If you need real per-person permissions, build them in your own service in front of the API — the shared inbox can't do it for you.
- No custom metadata as agent state. Don't try to stash your agent's bookkeeping on the messages. Keep workflow state — "draft #4 is awaiting Alice's approval since 2pm" — in your database, keyed by message and draft IDs. The mailbox holds mail; your app holds the workflow.
- Free-plan limits are real. 200 messages per account per day, 3 GB of storage per org, 30-day inbox retention (7 days for spam). Fine for a pilot; size your plan before a busy launch.
-
Deletes go to Trash by default.
DELETE /v3/grants/{grant_id}/messages/{message_id}trashes a message unless you pass?hard_delete=true. The CLInylas email deleteonly trashes. The only true full wipe of a mailbox is deleting the grant itself.
None of these are dealbreakers — they're just the edges. Knowing where they are means you won't design yourself into a corner expecting the inbox to be something it isn't.
What's next
- Connect mail clients to an Agent Account — the full IMAP/SMTP reference, the folder mapping, and the sync table.
- How Agent Account workspaces work — group multiple agent inboxes and apply shared policies.
- Agent Accounts overview — provisioning, policies, rules, and the rest of the data plane.
-
Nylas CLI command reference — every
nylascommand shown above, with its flags.
Build the agent against the API, hand your team an app password, and let the queue between them be a folder both sides can see. That's the whole shared inbox.
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/shared-inbox-api-for-ai-agents.md
- Industry playbooks hub: https://cli.nylas.com/ai-answers/agent-account-industry-playbooks.md
Top comments (0)