Attachments: what an agent should do with the file in the email

A surprising share of business email is a one-line message wrapped around a file. Here is the invoice. Please sign the attached. My CV is enclosed. An agent that reads bodies and ignores attachments handles the envelope and misses the content, which is why attachment handling shows up in almost every real deployment within a fortnight.

5 min read

An inbound message with an attachment being read by an agent
The message is the wrapper. Often the file is the entire point.

Receiving

Attachments are not delivered inline with the message, because a model does not want a base64 blob in its context and you do not want to pay for one. Listing a message tells you what is attached; fetching is a separate, deliberate call.

read_message   { message_id: "msg_..." }        // body plus attachment metadata
get_attachment { message_id: "msg_...", filename: "invoice-2291.pdf" }

get_attachment returns the file base64 encoded, and over REST the same thing is a plain GET /v1/messages/:id/attachments/:filename. The decision worth making explicitly is when to call it: fetch based on the metadata, not reflexively. A filename and content type are usually enough to decide whether this is the invoice you were waiting for or a 12 MB logo in a signature block.

Do not hand a raw file to a model

The step people skip: extract, then reason. A PDF is not text, a scanned PDF is an image, and a spreadsheet is a structure. Push the file through the right extractor first and give the model the result.

Arrives asDo this firstThen
PDF with a text layerExtract textRead normally
Scanned PDF or imageOCRRead, with lower confidence
SpreadsheetParse to rowsReason over values, not layout
Word or ODF documentConvert to textRead normally
ArchiveExpand, then apply the above per fileWatch the expansion size
Anything executableNothingEscalate or discard

This is also where cost control lives. Extract the three fields you need from a fifty page contract rather than pushing the whole thing through a model, and store the extraction rather than repeating it every time the thread is revisited.

Treat every attachment as hostile

An attachment is the least trusted input your agent will ever see, and it arrives from anyone who knows the address.

Content is not what the filename says. Check the actual type rather than the extension. A .pdf that is not a PDF is a signal in itself.

Files carry [prompt injection](/blog/prompt-injection-email-agents) as readily as bodies do, and more invisibly. Text in a document, in an image, in a spreadsheet cell, or in PDF metadata all end up in your model's context after extraction, and none of it is visible to a human skimming the thread. The defence is the same: extracted text is data, never instructions, and privileged actions live behind a check the model does not control.

Bounds matter. Cap the size you will fetch, the number of files per message, and how far you will expand an archive. Unbounded expansion is an old denial of service trick that works fine on new systems.

Never execute anything, and be careful with anything that fetches: a document with remote resources can call home the moment it is opened by the wrong tool.

Sending

Files go out on the same call that sends the message, base64 encoded, and the same shape works on reply_all.

send_email {
  to: ["ap@customer.com"],
  subject: "Invoice 2291",
  body: "Attached, due 8 August. Reply with a PO number if you need one added.",
  attachments: [{ filename: "invoice-2291.pdf", content_type: "application/pdf", content_base64: "JVBERi0..." }]
}

Three practical notes. Generate the file yourself rather than forwarding one you received, unless forwarding is the point, because you know what is in the one you made. Keep them small: mailbox providers and corporate gateways both dislike large attachments, and a link to a document with an access check is often better than the document. And name them for a human, since invoice-2291.pdf is findable next March and output_final_v2.pdf is not.

An agent extracting fields from an inbound PDF and replying with a generated one
Extract what you need, act on it, and send back something you generated yourself.

Forwarding, and what it does to the file

forward_email in wrapped mode keeps headers and attachments intact, which is what you want when a person downstream needs the original: an invoice going to accounts payable, a CV going to a hiring manager, a contract going to legal. What it does not do is make the forward harmless. Forwarding an attachment you have not inspected passes your judgement along with the file, and forwarding to someone who has not written to you is a cold send with all that implies, per keeping an autonomous agent from becoming a spam problem.

Where this fits the common use cases

  • [Invoice chasing](/blog/automated-invoice-chasing-ai-agent): inbound remittance advice and disputed invoices arrive as PDFs; outbound copies of invoices go the other way.
  • [Recruiting](/blog/ai-interview-scheduling-email-agent): CVs and portfolios, where extraction is the whole job and the file should reach a human intact.
  • [Support](/blog/ai-agent-customer-support-email): screenshots and logs, where OCR turns an image into something answerable.
  • [Sales](/blog/ai-sales-agent-lead-follow-up): security questionnaires and signed documents, both of which usually deserve a person.

Retention is a real decision

Attachments are the heaviest thing in an inbox and often the most sensitive. Storage is part of your plan's allowance, so archiving mail you have dealt with keeps both the inbox and the bill under control. More importantly, a CV or a signed contract sitting in an agent inbox indefinitely is a data protection question, so decide deliberately how long you keep them, and treat "forever, because deleting was never implemented" as the wrong answer.

Questions

Can an AI agent read email attachments?
Yes. read_message returns attachment metadata and get_attachment fetches one file base64 encoded, or GET /v1/messages/:id/attachments/:filename over REST. Extract text or structure before giving anything to a model.
Can an agent send attachments?
Yes, as { filename, content_type, content_base64 } on send_email or reply_all. Prefer files you generated, keep them small, and name them for a human.
Are attachments a security risk for agents?
They are the least trusted input an agent sees. Verify the real content type, bound size and expansion, never execute anything, and treat extracted text as data rather than instructions.
Should the agent fetch every attachment?
No. Decide from the metadata. Most signature-block images and duplicated logos are not worth the bytes or the tokens.
What about scanned documents?
Run OCR first and treat the result as lower confidence. An agent acting on an OCR misread of an amount is a good reason for the money-shaped escalation rules in the system prompt.
How long should attachments be kept?
As long as the job needs and no longer. They dominate storage and often carry personal data, so archive dealt-with mail and decide retention deliberately.

Give your agent an address it can answer from.

Create an inbox