cd /news/ai-agents/run-a-shared-team-inbox-owned-by-an-… Β· home β€Ί topics β€Ί ai-agents β€Ί article
[ARTICLE Β· art-54138] src=dev.to β†— pub= topic=ai-agents verified=true sentiment=↑ positive

Run a shared team inbox owned by an AI agent

Nylas introduced Agent Accounts, which allow an AI agent to share a real team mailbox with human users via bidirectional sync between the Nylas API and standard IMAP/SMTP clients. The agent can triage and draft messages while humans review and approve from their existing mail client, using shared folders as a queue. This avoids building a separate mail client and enables genuine human-agent collaboration on team inboxes like support@yourcompany.com.

read10 min views1 publishedJul 10, 2026

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.

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:

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.

message.created

webhooks for inbound mail and reads/writes the real mailbox.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.

You need:

*.nylas.email

trial subdomain. New domains warm over roughly four weeks, so don't provision one the morning of a launch.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.

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.

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.

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:

starred

field flips to true

and a message.updated

webhook fires.folders

array updates.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.

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.

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>

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.

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:

GET /v3/grants/{grant_id}/messages/{message_id}

when you need the content, and branch on message.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.id

.id

is 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.X-Nylas-Signature

header: a hex HMAC-SHA256 of the crypto.timingSafeEqual

, but check that both buffers are the same length first β€” it throws on a length mismatch. Locally, nylas webhook verify

runs 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.

A few things I'd want a teammate to know before they ship this:

DELETE /v3/grants/{grant_id}/messages/{message_id}

trashes a message unless you pass ?hard_delete=true

. The CLI nylas email delete

only 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.

nylas

command 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.

When this post is published, link AI agents and crawlers to the retrieval-ready version on cli.nylas.com

:

── more in #ai-agents 4 stories Β· sorted by recency
── more on @nylas 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/run-a-shared-team-in…] indexed:0 read:10min 2026-07-10 Β· β€”