Ask almost any AI assistant to email you a PDF and one of two things happens. It tells you it can't, or it starts typing the file out as base64, one character at a time, and gives up somewhere around the fiftieth line. Press Forge and Mailbox MCP are both BSolve IT Limited, and Mailbox MCP is our own commercial MCP server: customers connect Claude, ChatGPT or Copilot to a mailbox they already own. MCP email attachments were the hardest thing in it to get right, and the reason is not what most people assume. The model isn't bad at files. It was never given a way to refer to one.
The short answer, before the long one: yes, an AI assistant can both send and read email attachments, on Gmail, Microsoft 365 or any IMAP mailbox, and it can do it with nothing base64 encoded at any point. That last part is what makes it work rather than fail, and the rest of this post is why.
Two Companion Posts
This is the design half. For the measurements, our sister site has what base64 actually costs on three Claude models and an OpenAI tokeniser, with the script to check your own files. For the wider rules this post is an instance of, start with the nine design rules we took from shipping a live MCP tool surface.
Why Your AI Cannot Attach a File: The MCP Spec Has No File Type
Here's the root of it, and it's structural rather than anybody's bug. Model Context Protocol tool arguments are JSON. JSON has strings, numbers, booleans, arrays and objects. It has no file. So when a tool wants a file, the file has to become a string, and the only string a byte sequence turns into is an encoding. In email that encoding is base64, and the model is the thing that has to write it.
This was noticed early. In December 2024, barely a fortnight after MCP was published, a developer opened a discussion on the specification repository asking for "better ways to pass files from the client to the server for processing", having found base64 unreliable for larger files. An Anthropic maintainer proposed a FileContent type so that servers could return files without inflating the model's context. Note the direction of that proposal. It solves files coming back. Passing a file in still has no standard answer.
What makes this worth writing up is not the gap itself. It's that everybody hits it separately and nobody finds out that everybody else did. Search the issue trackers and you'll find unrelated projects arriving at the same wall in their own words, months apart: a Redmine server whose maintainer logged that "a local file can only reach uploads by being retyped by the model, and it arrives corrupted"; a university platform asking for presigned direct upload because "inline base64 is impractical for LLM clients"; another asking to move files "as files (signed URLs), not base64 in the tool call". Same discovery, four codebases, no shared answer.
We hit it too, in August 2026, and the internal note that came out of it is one sentence: base64 through tool arguments cannot carry a real attachment, because the model has to emit the base64 as output tokens. Everything below follows from taking that seriously instead of raising the limit and hoping.
What Base64 Email Attachments Cost, and the Figure We Got Wrong
Base64 itself is old, well specified and cheap. The overhead is fixed and modest, and the people who wrote the standard said so plainly:
The encoding and decoding algorithms are simple, but the encoded data are consistently only about 33 percent larger than the unencoded data.
Ned Freed and Nathaniel Borenstein, RFC 2045, MIME Part One, section 6.8, November 1996
Thirty-three percent. That's the number everyone reaches for, and in 1996 it was the only number that mattered, because the cost being counted was bandwidth and disk. A third more of either is annoying and survivable. What that sentence cannot tell you, because nothing like a language model existed when it was written, is that the expensive part is no longer the size of the encoded data. It's who does the encoding. A mail client encoding a file spends microseconds. A model encoding a file spends its entire reply.
And here is where we have a correction of our own to make, because we got this wrong in public. Mailbox MCP's documentation and its tool descriptions have said roughly 450,000 tokens per megabyte. That figure came from an internal brief in August that assumed base64 tokenises at about three characters per token, which is the familiar rule of thumb for ordinary English. Nobody checked it against base64. On 28 September we measured it properly, and base64 is nothing like English: a run such as Lj9MKMSAwIG9iago matches almost nothing a tokeniser has seen, so it shatters into tiny pieces. The measured rate is a shade over one token per character on Claude Sonnet 5 and Opus 5.
So the real figure for 1 MB is about 1.28 million tokens, not 450,000. We were out by nearly three times, in our own favour, for a month. The full method, the three Claude models, the OpenAI comparison and a script you can run on your own files are in the sister-site write-up on why your AI shouldn't have to type out a PDF to email it.
Put that against a hard limit and the argument finishes itself. One reply from Sonnet 5 is capped at 128,000 output tokens. A 1 MB attachment is therefore ten replies long before the email has a single word of its own in it, and base64 carries no error correction, so one wrong character anywhere in those ten replies spoils the file. That isn't an efficiency problem to be optimised. It's a design that cannot work, and raising the size limit makes it worse rather than better.
Four Routes In, None of Them Base64
The fix is to stop treating a file as data the model carries and start treating it as a thing the model points at. Every route below is a reference the server resolves at send time. The file is fetched when the message is built, not when the assistant is talking, so its size has almost no bearing on what the conversation costs.
There are four ways in, and an assistant picks between them the way you would:
// 1. Already in this mailbox. An invoice someone sent you, a photo, a contract.
// read_email lists each attachment with a ref; pass the ref straight back.
{ "fileRef": "att_01J9F2...": }
// 2. On the web. The server fetches the URL while building the message.
{ "url": "https://example.co.uk/terms-2026.pdf" }
// 3. On your own computer. create_upload_link mints a private drop link;
// every file that arrives comes back under one uploadId.
{ "uploadId": "upl_8c14..." }
// 4. Something the assistant generated that exists nowhere else.
// Base64, and deliberately kept small.
{ "filename": "summary.csv", "content": "TmFtZSxUb3RhbAo..." }
Route one is the one that surprises people, because it costs nothing at all. Forwarding an attachment you already have doesn't need the file to move anywhere: read_email already listed it, and the reference it gave you is enough to carry it onto a new message. Ask your assistant to forward Tuesday's invoice to your accountant and the attachment never enters the conversation in either direction.
Route three is the one that makes the difference in practice, and it's the one nobody else publishes. You say you want to send a file from your laptop, the assistant calls create_upload_link, and you get a private link to drop files on. Every file that lands comes back under a single uploadId, each keeping its own name. One small design detail is worth naming because we went back and forth on it: that route leaves a holding draft in your Drafts folder while it waits, and the draft removes itself once the files are attached. An assistant that left tidy-up jobs behind in your mailbox would fail the rule the whole product is built on, which is that you should not be able to tell from Outlook that a machine did the work.
The ceiling on all of this is 20 MB for one file and 20 MB for everything on a message together. That limit is ours only in the sense that we wrote it down. The real constraint is the receiving mail servers, which is why raising it would not help anybody.
Why We Kept a Base64 Route At All
Given everything above, keeping a base64 route looks like a contradiction. It isn't, and the reasoning is worth showing because it's the sort of judgement call that gets designed out by accident.
Sometimes the thing being attached doesn't exist anywhere yet. The assistant has just written a short CSV, or a two-paragraph text file, and there's no mailbox holding it, no URL serving it and no sense in asking a person to save it and re-upload it. For that, base64 is the correct answer. It's only the wrong answer at scale.
So route four stays, with a ceiling of roughly 50 KB, and the wording in the tool description is deliberate: that is a ceiling and not a target. The instruction the model reads says so in as many words, because an unqualified limit invites a model to fill it. If something won't fit at a quality worth receiving, the right move is to go back to the upload link and let a person drop the real file in, rather than degrade a file to squeeze it through the wrong door.
The refusals matter as much as the routes. Per rule seven of our tool design rules, an error message is agent-facing UI, so ours say what actually happened and what to do instead rather than just failing:
`Those attachments come to more than the ${megabytes(MAX_ATTACHMENTS_TOTAL_BYTES)} MB limit for
one message, so none of them was fetched and nothing was sent.`
"None of them was fetched and nothing was sent" is the load-bearing half of that sentence. A model that reads only "too big" will often try again with fewer files, which is fine, or assume a partial send happened, which is not. Telling it the state of the world is what stops it inventing one.
Can Claude Read Email Attachments? What read_attachment Returns
Sending is half the job. The other half is that until recently, an assistant reading your mail could see that a file was attached and nothing more. read_email lists attachments; it has never opened them. So we shipped read_attachment on 12 September 2026, and it takes the same refs the listing gave you and returns what is inside.
Documents come back as text. A PDF arrives with page markers. A Word document arrives as its text. An Excel workbook arrives as one CSV block per sheet with the formulas already worked out, which matters more than it sounds: the assistant sees the calculated values rather than the formula strings, so a total is a total. PowerPoint comes back slide by slide, and plain text, CSV, HTML and forwarded messages come back as themselves. Pictures come back as pictures the model can actually look at, and a photo too large for one tool result is shrunk to fit rather than refused.
The first real use of it was mundane in the best way. A supplier invoice, a trade association's annual subscription, sitting in our sales mailbox as a one-page PDF. One call returned 863 characters of text with a page marker, and net, VAT and total each on their own line. The VAT line checked as exactly twenty percent of the net. The whole exchange took one tool call, and the message stayed unread in the mailbox afterwards, which is the part I care about most: reading a file does not mark the mail as read, because deciding you have dealt with something is a judgement only you get to make.
There's a rule that only becomes visible once an assistant can open files, and it's the one I'd want a developer to take away from this section. The text inside a file is written by whoever sent it. A PDF is as good a carrier for "ignore your previous instructions" as an email body is, and it arrives with more of an air of authority because it looks like a document. Our reader treats a file's text as untrusted, exactly like the message it came with, so an instruction found inside a PDF is content to report rather than a command to follow. Anthropic's own guidance frames the wider version of this well:
Context, therefore, must be treated as a finite resource with diminishing marginal returns.
Anthropic Applied AI team, "Effective context engineering for AI agents"
I keep coming back to "finite resource", because it reframes a decision that looks technical as a decision about attention. Every megabyte of decoded file you push into a model is a megabyte of room that the person's actual question no longer has. That's why reading is windowed here: 50,000 characters by default, 200,000 at the ceiling for one call, with an offset to continue a long document. The point isn't rationing for its own sake. It's that a model given a whole contract at once answers worse than one given the clause you asked about.
Email Connectors and Attachments Compared
We check this periodically against each product's own documentation rather than against anybody's marketing, because it's a claim we make about ourselves and it needs to survive someone checking it. The table below is the state on 27 September 2026. Every cell comes from the vendor's own docs or changelog on that date.
| Connector | Mailbox file by reference | Web link | From your computer, no base64 | What it needs otherwise |
|---|---|---|---|---|
| Mailbox MCP | Yes | Yes | Yes, upload link | Base64 only under about 50 KB |
| MCP Emails | Yes, by message and index | Not stated | No route published | Base64; drafts take no attachments |
| MailMCP | Not stated | Yes, added 24 September | No, the AI supplies the bytes | Base64, chunked |
| AnyMailMCP | Only by forwarding whole | Not stated | Not stated | No attach route on send at all |
| Google Gmail MCP | Not stated | Not stated | No; over 25 MB it says use a Drive link | Base64 required; drafts unsupported |
| Claude Microsoft 365 connector | No | No | No | Attachments unsupported in any write tool |
So the claim, scoped exactly as we can defend it: of the six email connectors we compared on 27 September 2026, Mailbox MCP is the only one that lets your AI attach a file from your own computer without encoding it first. Two things that claim deliberately does not say. It doesn't say "the only email connector anywhere", because a self-hosted server running on your own machine can simply take a file path and we didn't check those. And it doesn't say "the only one that never needs base64", because that's trivially true of any connector that can't attach anything at all. A comparison you'd have to defend is worth more than one you'd have to walk back, and if you'd rather read the long version, we keep a fuller comparison of email MCP servers updated alongside it.
What Attachment Handling Here Does Not Do
Four honest limits, because a post arguing for defensible claims should be straight about its own.
Reading is read-only, and some things are named rather than opened. A zip, an RTF or an old-format .doc comes back as a sentence saying what it is, not as its contents. You still get a download link, but the assistant can't work with the inside of it.
A scanned page is a picture, and pictures are rationed. If a PDF page is a photograph of a document rather than text, it comes back as an image of that page, up to four pages a call. A fifty-page scanned contract is not a one-call job, and anyone telling you their assistant reads scanned documents as fast as digital ones is describing something else.
Text is capped per call. 50,000 characters by default and 200,000 at the ceiling, continued by offset. Long documents are read in windows on purpose, for the reason in the section above, but it does mean a very long document is several calls rather than one.
The comparison is a snapshot, not a standing fact. It was true of six products on one dated day, checked against their own documentation. MailMCP added web-link fetching three days before we last looked. Any of them could ship an upload route next month, and if one does, that table changes.
The Same Rule for WordPress Abilities
Press Forge builds WordPress, and the Abilities API puts every plugin developer in front of the same decision we've just described. The moment an ability touches a file, whether that's a media upload, a PDF export or an import, there's a fork in the road: does the ability accept the file, or a reference to it?
Accept the file and you've rebuilt the problem inside WordPress. Accept a reference and the model does what it's good at, which is choosing correctly between things it can name.
<?php
// Wrong. The model has to produce the bytes, so this ability
// works in a test and fails on anything a person would actually send.
'input_schema' => [
'type' => 'object',
'properties' => [
'file_contents' => [ 'type' => 'string', 'description' => 'Base64 of the file' ],
],
],
// Right. The file is already in the media library, so name it.
// The model picks an ID. WordPress moves the bytes.
'input_schema' => [
'type' => 'object',
'properties' => [
'attachment_id' => [
'type' => 'integer',
'description' => 'ID of an existing media library item. Use '
. 'pressforge/search-media to find one by filename or date. '
. 'This ability never creates media and never uploads.',
],
],
'required' => [ 'attachment_id' ],
],
The second version needs a companion ability that finds media, which feels like more work until you notice it's the pattern every desktop application already uses. Nobody retypes a file into an email client. They click Attach, and a picker hands over a path. An attachment_id is that picker, and a model is considerably better at choosing from a list than at transcribing a megabyte without a typo.
If you're weighing up how much of your site to expose to an agent at all, the nine rules cover the surface design and the security post covers what to lock down first. It's also worth knowing that WordPress 7 ships AI support with no spend cap in core, which is its own reason to care what your abilities ask models to generate.
Frequently Asked Questions
Can Claude read email attachments?
Not on its own. Claude's built-in Gmail and Microsoft 365 connectors return the message body and list what is attached, but they don't open the file. Reading the contents needs an MCP server that extracts the text server-side and hands back the words rather than the bytes. Ours does that for PDF, Word, Excel, PowerPoint, plain text, CSV, HTML and forwarded messages, and returns pictures as pictures the model can look at.
Why can't Claude attach files to an email?
Because MCP tool arguments are JSON, and JSON has no file type. A file can only travel as a string, which in practice means base64, and base64 has to be written out by the model one token at a time. Most connectors either demand that or decline to attach anything at all. Claude's own Microsoft 365 connector documentation says attachments aren't supported in any write tool.
How do I get an AI to send an email with an attachment?
Give it a way to name the file instead of retyping it. Through Mailbox MCP you ask in plain English: forward the invoice that came in on Tuesday, attach the PDF at this web address, or send the file you're about to drop on the upload link. The assistant passes a reference and the server fetches the bytes at send time, so nothing large passes through the conversation.
How big an attachment can an AI send?
Through a reference route, as big as the mail system allows. Our ceiling is 20 MB for one file and 20 MB for everything on one message together, which is set by the receiving mail servers rather than by the model. Through a base64 route the practical ceiling is far lower, roughly 50 KB, because the model has to emit every character of the encoding itself.
Does sending an email attachment through AI cost tokens?
It depends entirely on how the connector is built. If the model writes the file out as base64, a 1 MB file measures at about 1.28 million output tokens on Claude Sonnet 5 and Opus 5, because base64 tokenises at roughly one token per character. If the model passes a reference and the server moves the bytes, the same attachment costs a few hundred tokens: a filename and a handle.
Can AI read PDF, Word and Excel attachments?
Yes, where the connector extracts them server-side. A PDF comes back as text with page markers, a Word document as text, and an Excel workbook as one CSV block per sheet with the formulas already worked out, so the assistant sees the calculated values rather than the formula strings. A PDF page that is a scan comes back as a picture of the page instead, because there's no text in it to extract.
Is it safe to let an AI read my email attachments?
Reading is read-only, nothing is stored, and the file never leaves the mailbox. The risk worth knowing about is different: text inside a file is written by whoever sent it, so a PDF can carry an instruction aimed at your assistant. Our reader treats a file's text as untrusted, exactly like the message it arrived with, and an instruction found inside one is something to report rather than act on. There's more on the boundaries in the Mailbox MCP security notes.
Do I need to write code to send attachments from my AI?
No. Connecting a mailbox is a setup step in the AI client, and after that you ask in plain English. The design decisions in this article are why it works without code, not something you have to implement. Developers building their own tool surface will find the pattern useful, which is the second half of this post.
Send Your Next Attachment From Your AI
Mailbox MCP connects Claude, ChatGPT or Copilot to a mailbox you already own, on Gmail, Microsoft 365 or any IMAP host. Nothing to migrate, and no message content stored. The free tier is five calls a day per mailbox, so you can test the routes in this post before paying for anything.
See How Attachments WorkPublished: 30 September 2026 · Last reviewed: 30 September 2026 · Written by: Mark McNeece, Founder & Lead Developer, Press Forge
Editorially reviewed by: Mark McNeece on 30 September 2026 · Our editorial standards
Sources
- RFC 2045: MIME Part One, section 6.8 - Ned Freed and Nathaniel Borenstein, IETF (November 1996)
- Suggested way to pass files from the client - Model Context Protocol specification discussion (December 2024)
- Effective context engineering for AI agents - Anthropic Applied AI team
- Model Context Protocol - Official specification
- Claude Microsoft 365 connector: attachment support - Anthropic support documentation
- Your AI Shouldn't Have to Type Out a PDF to Email It - AI Visibility (token measurements, 28 September 2026)
- Email attachments your AI can actually send and read - Mailbox MCP
- Abilities API - WordPress Common APIs Handbook