{"slug": "connect-legacy-tools-to-an-agent-mailbox-over-imap-smtp", "title": "Connect legacy tools to an agent mailbox over IMAP/SMTP", "summary": "Nylas has introduced Agent Accounts that expose the same mailbox over IMAP and SMTP submission alongside its v3 API, enabling legacy tools that only support email protocols to interact with the same mailbox as AI agents. The feature allows developers to connect ticketing systems, backup scripts, and monitoring tools without rewriting them to use HTTP APIs. Nylas hosts the mailbox, manages deliverability, and eliminates the need for OAuth token refreshes, though the mailbox is Nylas-hosted rather than tied to existing Google Workspace or Microsoft 365 accounts.", "body_md": "Most \"AI email\" integrations assume everything on the other side speaks REST. You wire up a webhook, you call `POST /messages/send`\n\n, and you move on. That works right up until you remember how much of your stack *doesn't* speak REST and never will: the ticketing system that ingests mail over IMAP, the backup script your predecessor wrote in 2014, the monitoring tool that only knows how to send SMTP, the compliance archiver that polls a mailbox every five minutes. None of those are getting rewritten to call an HTTP API for your demo.\n\nSo here's the trick that makes a Nylas **Agent Account** genuinely useful in a real environment: it's not API-only. You can expose the *same* mailbox over **IMAP** and **SMTP submission**, hand the host, port, and credentials to one of those legacy tools, and let it read and send like it's talking to any old mail server. Meanwhile your agent drives that identical mailbox over the v3 API. Both surfaces hit one storage layer. A flag, move, or delete on either side shows up on the other within seconds.\n\nI work on the Nylas CLI, so the terminal commands below are the exact ones I reach for. As usual I'll show both angles for every operation — the raw `curl`\n\nagainst the API and the `nylas`\n\ncommand — because half the point of an Agent Account is that you can mix them freely.\n\nAn Agent Account is just a **grant**. It has a `grant_id`\n\n, and that `grant_id`\n\nworks with every grant-scoped endpoint you already know — Messages, Drafts, Threads, Folders, Attachments, Contacts, Calendars, Events. There's nothing new to learn on the data plane.\n\nThe IMAP/SMTP layer doesn't change that model. It adds a second door into the same room:\n\nThat last point is the one that surprises people, so I'll be blunt about it later: this is shared mutable state across two protocols. It's a feature, but you should design for it.\n\nYou could, in theory, stand up Dovecot, bolt on Postfix, wire in DKIM and SPF and DMARC, warm an IP, and expose that to your legacy tools yourself. People do. It's also a multi-week project with an on-call rotation attached.\n\nWith an Agent Account you skip all of it. Nylas hosts the mailbox, runs the IMAP and SMTP submission servers, handles TLS, manages deliverability, and gives you the API on top. The part I like as an SRE: there's no OAuth token to refresh. A connected Gmail or Microsoft grant lives and dies by its refresh token. An Agent Account has no upstream provider, so the grant doesn't silently expire on you at 3 a.m. — it just keeps being a mailbox.\n\nThe honest tradeoff: this is a Nylas-hosted mailbox, not your existing Google Workspace or Microsoft 365 account. If your goal is to put an agent on a *human's* real inbox, that's a connected-grant problem, not this. Agent Accounts are for mailboxes the agent (and your tooling) *owns* — `support@`\n\n, `notifications@`\n\n, `intake@`\n\n, the address a workflow lives behind.\n\nYou need three things:\n\n`*.nylas.email`\n\ntrial subdomain. New domains warm over roughly four weeks before they're sending at full reputation.`grant_id`\n\n.If you don't have the account yet, create one. Over the API it's a `POST /v3/connect/custom`\n\nwith `\"provider\": \"nylas\"`\n\n:\n\n```\ncurl --request POST \\\n  --url \"https://api.us.nylas.com/v3/connect/custom\" \\\n  --header \"Authorization: Bearer $NYLAS_API_KEY\" \\\n  --header \"Content-Type: application/json\" \\\n  --data '{\n    \"provider\": \"nylas\",\n    \"name\": \"Support Bot\",\n    \"settings\": {\n      \"email\": \"support@yourcompany.com\"\n    }\n  }'\n```\n\nThe response carries the `grant_id`\n\n. The optional top-level `name`\n\nsets the display name on outbound mail. No refresh token, no redirect dance.\n\nFrom the CLI it's one line:\n\n```\nnylas agent account create support@yourcompany.com --name \"Support Bot\"\n```\n\nThe API auto-creates a default workspace and policy for the account, so you don't pass a workspace on create — there's no `--workspace`\n\nflag. If you later want a custom policy governing limits and spam, you attach it to the workspace with `nylas workspace update <workspace-id> --policy-id <policy-id>`\n\n.\n\nNew to Agent Accounts in general? The [Agent Accounts docs](https://developer.nylas.com/docs/v3/agent-accounts/) walk through the model end to end. Come back here once you have a `grant_id`\n\nin hand.\n\nHere's the thing that trips people up first: **API access and protocol access use different credentials.** The API authenticates with your Nylas API key and a `grant_id`\n\n. IMAP and SMTP can't use that — a mail client only knows how to send a username and a password. So protocol access has its own credential: an **app password** set directly on the grant.\n\nUntil you set one, Nylas rejects every IMAP `LOGIN`\n\nand SMTP `AUTH`\n\nattempt. The API still works fine without it; the app password gates *only* the protocol door. Set it before you hand connection details to any legacy tool.\n\nThe password has rules, and Nylas validates them on write:\n\nNylas stores it as a bcrypt hash, so you can't read it back later — only reset it. Treat it like any other secret: generate it, stash it in your secrets manager, hand it to the client.\n\nYou can set the app password at creation time. Over the CLI, pass `--app-password`\n\n:\n\n```\nnylas agent account create support@yourcompany.com \\\n  --name \"Support Bot\" \\\n  --app-password \"MySecureP4ssword2024\"\n```\n\nOver the API, drop it into `settings`\n\non the same `POST /v3/connect/custom`\n\ncall:\n\n```\ncurl --request POST \\\n  --url \"https://api.us.nylas.com/v3/connect/custom\" \\\n  --header \"Authorization: Bearer $NYLAS_API_KEY\" \\\n  --header \"Content-Type: application/json\" \\\n  --data '{\n    \"provider\": \"nylas\",\n    \"name\": \"Support Bot\",\n    \"settings\": {\n      \"email\": \"support@yourcompany.com\",\n      \"app_password\": \"MySecureP4ssword2024\"\n    }\n  }'\n```\n\nWhen the app password leaks, or you're rotating on schedule, you set a new one on the grant you already have.\n\nFrom the CLI, `agent account update`\n\ntakes the same flag and looks the account up by `grant_id`\n\nor by email:\n\n```\nnylas agent account update support@yourcompany.com \\\n  --app-password \"MyRotatedP4ssword2025\"\n```\n\nOver the API, `PATCH /v3/grants/{grant_id}`\n\ndoes it — but note one sharp edge: a `PATCH`\n\nreplaces the *entire* `settings`\n\nobject, so you have to send `email`\n\nalongside the new `app_password`\n\nor you'll wipe it out.\n\n```\ncurl --request PATCH \\\n  --url \"https://api.us.nylas.com/v3/grants/$NYLAS_GRANT_ID\" \\\n  --header \"Authorization: Bearer $NYLAS_API_KEY\" \\\n  --header \"Content-Type: application/json\" \\\n  --data '{\n    \"settings\": {\n      \"email\": \"support@yourcompany.com\",\n      \"app_password\": \"MyRotatedP4ssword2025\"\n    }\n  }'\n```\n\nRotating disconnects every active IMAP and SMTP client immediately — each one reconnects after the user (or the config) supplies the new value. Plan rotations for a quiet window if a legacy tool can't gracefully re-auth.\n\nOnce the app password is set, point any IMAP/SMTP tool at the Nylas servers for your region. The credentials are the account's own email address and the app password.\n\n| Setting | Value |\n|---|---|\n| IMAP host |\n`mail.us.nylas.email` (US) · `mail.eu.nylas.email` (EU) |\n| IMAP port |\n`993` (implicit TLS) |\n| SMTP host |\n`mail.us.nylas.email` (US) · `mail.eu.nylas.email` (EU) |\n| SMTP port |\n`465` (implicit TLS) or `587` (STARTTLS) |\n| Username | The Agent Account email, e.g. `support@yourcompany.com`\n|\n| Password | The `app_password` you set on the grant |\n| Encryption | TLS required on every port |\n\nIn a desktop client like Thunderbird, Apple Mail, or Outlook, that's just the manual-setup screen: server `mail.us.nylas.email`\n\n, IMAP 993 SSL/TLS, SMTP 465 SSL/TLS (or 587 STARTTLS), username and password as above.\n\nFor a script, it's whatever your language's IMAP library wants. The point is that it's *ordinary* — here's a throwaway Python check that the credentials work and counts what's in the inbox:\n\n``` python\nimport imaplib\n\nhost = \"mail.us.nylas.email\"\nuser = \"support@yourcompany.com\"\napp_password = \"MySecureP4ssword2024\"  # pull from your secrets manager\n\nwith imaplib.IMAP4_SSL(host, 993) as imap:\n    imap.login(user, app_password)\n    imap.select(\"INBOX\")\n    typ, data = imap.search(None, \"ALL\")\n    print(\"messages in inbox:\", len(data[0].split()))\n```\n\nNo SDK, no API key, no `grant_id`\n\nin sight. That's the legacy tool's whole world, and it's enough.\n\nThis is the part worth internalizing, so let me make it concrete. The IMAP client above and the API are looking at one mailbox. Watch what happens when each side acts.\n\nList the inbox over the API:\n\n```\ncurl --request GET \\\n  --url \"https://api.us.nylas.com/v3/grants/$NYLAS_GRANT_ID/messages?in=inbox&limit=5\" \\\n  --header \"Authorization: Bearer $NYLAS_API_KEY\"\n```\n\nOr the CLI equivalent, which resolves the Agent Account grant for you:\n\n```\nnylas email list --folder INBOX\n```\n\nNow flag a message as read from the IMAP side with a `STORE \\Seen`\n\n, or move it to another folder. Re-run that same `nylas email list`\n\nor `GET /messages`\n\nand the `unread`\n\nand `folders`\n\nfields already reflect it. There's no propagation delay you need to code around — it's the same row.\n\nIt runs the other direction too. Send from the API:\n\n```\ncurl --request POST \\\n  --url \"https://api.us.nylas.com/v3/grants/$NYLAS_GRANT_ID/messages/send\" \\\n  --header \"Authorization: Bearer $NYLAS_API_KEY\" \\\n  --header \"Content-Type: application/json\" \\\n  --data '{\n    \"to\": [{ \"email\": \"customer@example.com\" }],\n    \"subject\": \"Re: your ticket\",\n    \"body\": \"Thanks — we are on it.\"\n  }'\nnylas email send --to customer@example.com \\\n  --subject \"Re: your ticket\" \\\n  --body \"Thanks — we are on it.\"\n```\n\nThat message lands in the **Sent** folder your IMAP client sees, exactly as if the client had submitted it over SMTP. And a message submitted over SMTP shows up in the API's message list the same way. Replying in-thread works from either side; from the CLI, `nylas email reply <message-id> --body \"...\"`\n\npreserves the `In-Reply-To`\n\nand `References`\n\nheaders so the conversation stays threaded.\n\nThe folder mapping is the obvious one: IMAP `INBOX`\n\n/`Sent`\n\n/`Drafts`\n\n/`Trash`\n\n/`Junk`\n\n/`Archive`\n\nmap to the API folder IDs `inbox`\n\n/`sent`\n\n/`drafts`\n\n/`trash`\n\n/`junk`\n\n/`archive`\n\n. Custom folders you create over either surface appear on the other. IMAP `APPEND`\n\neven deduplicates on the MIME `Message-ID`\n\n, so a message that's already in the mailbox (because SMTP just put it there) won't get a phantom copy.\n\nBecause both surfaces share storage, both fire the **same webhooks** — and this is how your agent stays in the loop on what the legacy tool is doing. A human marks a message read in Thunderbird? `message.updated`\n\n. The archiver moves something? `message.updated`\n\n. New inbound mail, whether your agent or the IMAP client sees it first? `message.created`\n\n.\n\nWebhooks are **application-scoped**, not grant-scoped. You subscribe once at the app level and every grant's events arrive at that endpoint, each payload carrying a `grant_id`\n\nyou filter on:\n\n```\ncurl --request POST \\\n  --url \"https://api.us.nylas.com/v3/webhooks\" \\\n  --header \"Authorization: Bearer $NYLAS_API_KEY\" \\\n  --header \"Content-Type: application/json\" \\\n  --data '{\n    \"trigger_types\": [\"message.created\", \"message.updated\"],\n    \"webhook_url\": \"https://your-app.example.com/nylas/webhooks\",\n    \"description\": \"Agent mailbox events\"\n  }'\nnylas webhook create \\\n  --url https://your-app.example.com/nylas/webhooks \\\n  --triggers message.created,message.updated\n```\n\nA few facts to build correctly against, all of which I've watched people get wrong:\n\n`GET /v3/grants/{grant_id}/messages/{message_id}`\n\nwhen you actually need the content. Also branch on `message.created.truncated`\n\n— that's the variant you get when a body exceeds ~1 MB and gets omitted.`id`\n\n.`id`\n\nis constant across all retries of one event — that's your dedup key. The inner `data.object.id`\n\n(the message id) identifies the message itself; you can additionally guard on it to avoid acting twice on the same message.`X-Nylas-Signature`\n\nheader: a hex HMAC-SHA256 of the `crypto.timingSafeEqual`\n\n, check both buffers are the same length first, because it throws on a length mismatch. The CLI's `nylas webhook verify`\n\ndoes this check locally while you're developing.Agent Accounts also emit deliverability webhooks for outbound mail — `message.delivered`\n\n, `message.bounced`\n\n, `message.complaint`\n\n, and `message.rejected`\n\n. Worth noting: `message.rejected`\n\nfires specifically when an outbound message is rejected because an attachment contained a virus, not as a generic catch-all rejection signal.\n\nA few honest gotchas before you ship this.\n\n**Shared mutable state is real.** Two protocols writing one mailbox means you can race yourself. If your agent auto-archives messages over the API at the same moment a human is reading them in a client, the message moves out from under them. Decide who owns what. A common split: the agent handles triage and labeling, humans handle replies — or the agent only touches messages in a folder humans don't live in.\n\n**Attachments don't go through SMTP from the API side.** When the agent sends *with* an attachment, that's the Messages or Drafts API, not SMTP. You attach either as Base64 strings in the JSON `attachments`\n\narray, or via multipart where the file form field is named `attachment`\n\n— **not** `file`\n\n, which is the single most common mistake. Note that `nylas email send`\n\nhas no attachment flag; for attachments from the CLI you stage a draft with `nylas email drafts create --attach`\n\nand send that.\n\n**Outbound size cap.** SMTP submission enforces a 25 MB ceiling on outbound messages at the `DATA`\n\ncommand. Over it and the send is rejected with an error naming the limit.\n\n**Connection limits exist.** There's a default cap of 20 concurrent IMAP connections per grant, a 30-minute IDLE timeout (the client re-issues IDLE), and a 5-minute timeout on otherwise-idle TCP connections. If your legacy tool opens a connection per worker, count your workers.\n\n**Free-plan ceilings.** The free plan allows 200 messages per account per day, 3 GB of storage per org, and retains the inbox 30 days and spam 7 days. Fine for building; size up before production volume.\n\n**Rotation disconnects clients.** Said it above, repeating it because it's an operational footgun: rotating the app password drops every live session. Coordinate it with whoever owns the legacy tool's config.\n\nThe pattern here is small but powerful: an Agent Account gives you a mailbox your agent drives over a clean v3 API, *and* a standard IMAP/SMTP endpoint for every tool that will never call REST — both pointed at the same storage, both firing the same webhooks. You don't have to choose between \"modern API\" and \"works with the stuff I already run.\" You get both doors into one room.\n\nFrom here:\n\n`nylas agent`\n\n, `nylas email`\n\n, and `nylas webhook`\n\nflag.Set the app password, point one real client at the mailbox, and watch a flag you flip in the API show up in the client a second later. That moment is when the two-doors model clicks.\n\nWhen this post is published, link AI agents and crawlers to the retrieval-ready version on `cli.nylas.com`\n\n:", "url": "https://wpnews.pro/news/connect-legacy-tools-to-an-agent-mailbox-over-imap-smtp", "canonical_source": "https://dev.to/mqasimca/connect-legacy-tools-to-an-agent-mailbox-over-imapsmtp-14ch", "published_at": "2026-07-17 21:18:08+00:00", "updated_at": "2026-07-17 21:28:53.355825+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-infrastructure"], "entities": ["Nylas", "IMAP", "SMTP", "Google Workspace", "Microsoft 365"], "alternates": {"html": "https://wpnews.pro/news/connect-legacy-tools-to-an-agent-mailbox-over-imap-smtp", "markdown": "https://wpnews.pro/news/connect-legacy-tools-to-an-agent-mailbox-over-imap-smtp.md", "text": "https://wpnews.pro/news/connect-legacy-tools-to-an-agent-mailbox-over-imap-smtp.txt", "jsonld": "https://wpnews.pro/news/connect-legacy-tools-to-an-agent-mailbox-over-imap-smtp.jsonld"}}