How do I send email attachments from an AI agent?
By Naol Ketema, founder · · Markdown
SHORT ANSWER
Include each file in the send request with a filename, a content type and the file's bytes, base64-encoded on the wire. With the Agent Identity TypeScript SDK you pass the bytes as a Uint8Array and the SDK handles encoding; over HTTP you send content_base64. For large files, send a link instead, because inline base64 adds about a third to the payload size.
How email attachments work
An email with attachments is a MIME multipart message. The body text is one part, and each attachment is another part with a Content-Type, a filename in its Content-Disposition, and the file bytes encoded as base64. An inline image is an attachment with a Content-ID that the HTML body references as cid:.
Your agent does not need to build MIME by hand. It needs to give the mail API three things per file: a filename, a content type, and the bytes.
Sending an attachment with the SDK
In the Agent Identity TypeScript SDK, attachments are Uint8Array bytes. Read the file, pass it in, and the SDK converts it to the wire format.
import { readFile } from "node:fs/promises";
import { AgentClient } from "@agentidentity/sdk";
const client = new AgentClient({ baseUrl: "https://api.agent-identity.dev", apiKey: process.env.AID_KEY! });
await client.mail.send({
identityId: agent.id,
to: "customer@example.com",
subject: "Your invoice",
text: "Attached.",
attachments: [
{ filename: "invoice.pdf", contentType: "application/pdf", content: await readFile("invoice.pdf") },
],
idempotencyKey: "invoice-1142",
});If the agent generated the file in memory, for example a CSV or a PDF from a template, pass those bytes directly; there is no need to write a temporary file. Attachments also work on drafts, so a person can review the file before the message goes out.
send resolves when the message is accepted, not when it is delivered. Messages with attachments are the ones most likely to be refused by a strict receiving server, so watch for mail.failed, mail.bounced and mail.delivered events on anything that matters, such as an invoice or a signed contract. The bounce guide shows how to wait on one message's outcome.
Sending over HTTP
On the wire, attachments go inline in the send body, base64-encoded. filename and content_type are required.
curl -sS -X POST "https://api.agent-identity.dev/v1/identities/$AID_AGENT/mail/send" \
-H "authorization: Bearer $AID_KEY" \
-H 'content-type: application/json' \
-d "{
\"to\": [\"customer@example.com\"],
\"subject\": \"Invoice\",
\"text\": \"Attached.\",
\"attachments\": [{
\"filename\": \"invoice.pdf\",
\"content_type\": \"application/pdf\",
\"content_base64\": \"$(base64 < invoice.pdf | tr -d '\n')\"
}]
}"Embedding an image in the HTML body
To show an image inside the message rather than as a separate file, give the attachment a content_id and reference it from the HTML as cid: followed by that id.
{
"to": ["customer@example.com"],
"subject": "Your weekly chart",
"html": "<p>This week:</p><img src=\"cid:chart\" alt=\"Weekly chart\">",
"text": "This week's chart is attached.",
"attachments": [
{ "filename": "chart.png", "content_type": "image/png", "content_id": "chart", "content_base64": "iVBORw0KGgo…" }
]
}Always include a text body as well. Some clients block images by default, and a plain-text part is what many filters and screen readers rely on.
Large files: send a link
Because the payload is inline and base64 inflates it by about a third, inline attachments are not the path for very large files. Receiving servers also cap total message size, and large messages are more likely to be rejected or filtered. For big exports, recordings or archives, upload the file to your own storage and send a link with an expiry.
Reading attachments on inbound mail
Agents often need the other direction too: a customer sends a PDF and the agent must read it. Inbound attachments are extracted when the message is ingested and stored by the service, so they are available as soon as the mail.received event fires. Webhook deliveries for mail events include has_attachments in the message summary, so you can skip the lookup when there are none.
# List a message's attachments
curl -sS "https://api.agent-identity.dev/v1/identities/$AID_AGENT/mail/messages/$MSG/attachments" \
-H "authorization: Bearer $AID_KEY"
# Download one (follow redirects)
curl -sSL -o invoice.pdf \
"https://api.agent-identity.dev/v1/identities/$AID_AGENT/mail/messages/$MSG/attachments/$ATT/download" \
-H "authorization: Bearer $AID_KEY"Depending on the storage backend, the download endpoint either streams the bytes or redirects to a presigned URL, so follow redirects. In the SDK, client.mail.listAttachments and client.mail.downloadAttachment handle both cases, and downloadAttachment always returns bytes. You never handle storage credentials yourself.
Practical checks before an agent sends a file
- Set the right content type. application/pdf, text/csv and image/png render properly; a wrong type makes clients treat the file as unknown.
- Use a filename the recipient will understand, not a temp-file name.
- Never let the model choose which local file to attach from free text. Map its choice to an allowlist of files the agent is meant to send.
- Treat inbound attachments as untrusted. Parse them in a sandbox and do not follow instructions found inside them.
- Use an idempotency key derived from what triggered the send, so a retry does not send the same invoice twice.
FAQ
What format do attachments use in the Agent Identity API?
Over HTTP, each attachment has filename, content_type and content_base64, with an optional content_id. In the TypeScript SDK you pass filename, contentType and content as Uint8Array bytes.
Is there a size limit on attachments?
Attachments are sent inline and base64 adds about a third to their size, so they are not meant for very large files, and receiving servers cap message size too. Send a link for large files.
How do I embed an image instead of attaching it?
Give the attachment a content_id and reference it from the HTML body as cid: followed by that id.
How does an agent read a file someone emailed it?
List the message's attachments, then download one. The SDK's downloadAttachment returns the bytes whether the storage backend streams them or redirects.
Related guides
Give your agent its own identity in a couple of minutes. Get started free → · Docs · All guides