An invoice-processing agent receives an email at 2 a.m. The PDF it needs is attached — along with a 60 MB screen recording the sender included "for context." Whether your agent handles that gracefully or chokes on it depends entirely on decisions you made weeks earlier: what your mailbox accepts, what it drops, and how your code pulls files out.
Here's the full attachment path for a Nylas Agent Account — the hosted, API-controlled mailboxes currently in beta — covering inbound downloads, outbound sends, and the policy limits that act as your first line of defense.
When mail arrives, attachment IDs ride along on the message object. The download itself is one GET, streaming the raw bytes:
curl --request GET \
--url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/attachments/<ATTACHMENT_ID>/download?message_id=<MESSAGE_ID>" \
--header "Authorization: Bearer <NYLAS_API_KEY>"
Note the message_id
query parameter — attachments are addressed in the context of the message that carried them. If you want metadata before committing to the download (filename, content type, size), hit GET /v3/grants/{grant_id}/attachments/{attachment_id}
first. For an agent deciding whether a file is worth processing, checking the content type before streaming megabytes is the obvious move.
The typical agent flow chains three calls: message.created
webhook fires → fetch the full message to get attachment IDs → download the ones that match what you're looking for.
This is the part most attachment guides skip. Before your webhook ever fires, the grant's policy decides which attachments survive. Three limits control inbound files:
limit_attachment_size_limit
— max size per attachment, in byteslimit_attachment_count_limit
— max number of attachments per messagelimit_attachment_allowed_types
— an allowlist of MIME typesA policy for that invoice agent might cap attachments at 25 MB (26214400
bytes) and 20 files per message:
curl --request POST \
--url "https://api.us.nylas.com/v3/policies" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{
"name": "Invoice agent policy",
"limits": {
"limit_attachment_size_limit": 26214400,
"limit_attachment_count_limit": 20
}
}'
Policies apply through workspaces: attach the policy_id
to a workspace and every Agent Account in it inherits the limits. Every limit is optional — omit one and it defaults to your plan's maximum. Two scoping details save you confusion later:
PATCH
on the grant:
curl --request PATCH \
--url "https://api.us.nylas.com/v3/grants/<GRANT_ID>" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{ "workspace_id": "<WORKSPACE_ID>" }'
That's how you give the invoice agent strict attachment limits while a different workspace runs a support agent with looser ones — separate workspaces, separate policies, same application. One more guard: if you request a limit above your plan's maximum, the API rejects the policy with an error rather than silently clamping it.
The enforcement behavior is worth internalizing, because it's gentler than you might expect: over-limit attachments are dropped from the stored message, but the message itself still arrives and message.created still fires. Your agent gets the email text minus the oversized file. That 60 MB screen recording disappears; the invoice PDF under the cap comes through. No bounce, no silent message loss — just the offending payload stripped.
For an autonomous reader, that's exactly the failure mode you want. The model still sees the sender's words and can reply "your attachment was too large, please resend under 25 MB" instead of the conversation dead-ending.
One asymmetry to remember: these limits apply to inbound mail only. They're never applied to the stored sent copy, so anything your agent sends keeps all of its attachments in the sent folder regardless of what the policy says about incoming files.
Sending uses the same endpoint as any other message — POST /v3/grants/{grant_id}/messages/send
— as a JSON body, or multipart when attachments are involved. The hard ceiling is 40 MB total outbound message size, enforced on every send path per the mailbox documentation.
That 40 MB is your limit, not the recipient's. Remote mail servers commonly enforce lower caps (around 25 MB is typical), so a message that clears your side can still bounce at the destination. If your agent regularly ships large files, sending a link instead of an attachment is the deliverability-friendly answer — and it sidesteps the size lottery entirely.
When a send does fail, the deliverability webhooks (message.send_failed
, message.bounce_detected
) tell you, so the agent can fall back rather than assume delivery.
How do I find the messages that actually have attachments? The messages list endpoint takes a has_attachment
filter, so a batch agent can query GET /messages?has_attachment=true&in=inbox
instead of fetching everything and checking each object. Combine it with received_after
for incremental sweeps.
Did a rule eat my attachment, or did the policy? Rules don't strip attachments — they match sender fields and either block the whole message or route it. Attachment dropping is purely the policy's limits. If a whole message never arrived, check GET /v3/grants/{grant_id}/rule-evaluations
to see whether an inbound block
rule rejected it at SMTP; if the message arrived minus a file, that's the policy.
Do attachment limits affect what my agent sends? No. Policy attachment limits are inbound-only and are never applied to the stored sent copy. Outbound, the only attachment-relevant constraint is the 40 MB total message size.
A sane attachment setup for a production agent looks like:
message_id
,The interaction between these layers is the design insight: the policy handles the adversarial cases (junk payloads, malware-sized files, attachment bombs) before any code runs, so your application logic only ever sees attachments that are plausibly legitimate. Cheap defense, applied at the infrastructure level.
Spin up a test mailbox with the quickstart, email it a few files from your own account, and watch what the policy lets through. Then try emailing it something over your size limit — seeing the message arrive with the attachment stripped makes the whole model click.