Attachments
Attach files to a send with base64 content, embed inline images with cid, and stay inside the 40 MiB message ceiling.
Any send can carry an attachments array. Each entry is a file: a name, the bytes as base64, and optionally a MIME type and a Content-ID. A message with at least one usable attachment is assembled as a full MIME message rather than the plain subject-and-body form.
The attachment object
| Field | Type | Required | Behavior |
|---|---|---|---|
filename | string | yes | The name the recipient sees. An entry without a string filename is dropped. |
content | string | yes | The file's bytes, base64-encoded. Bare base64 only, with no data: URI prefix and no newline requirements. An entry without a string content is dropped. |
contentType | string | no | MIME type, for example application/pdf. Omit it and the type is derived from filename. |
cid | string | no | Content-ID. Setting it makes the part inline rather than a listed attachment. See Inline images. |
contentType and cid are camelCase, unlike the snake_case fields elsewhere in the send body. The array is passed through to the mail assembler untouched, so content_type is not read and the type falls back to whatever filename implies.
Attach a file
curl -X POST https://api.mailfully.com/v1/emails \
-H "Authorization: Bearer mf_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"from": "billing@mail.acme.com",
"to": "you@yourcompany.com",
"subject": "Invoice 4212",
"text": "Invoice 4212 is attached.",
"attachments": [
{
"filename": "invoice-4212.pdf",
"content": "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9UeXBlL...",
"contentType": "application/pdf"
}
]
}'The response is the standard 202 acceptance. Attachments are not echoed back, the same way html and text are not.
In the SDK, attachments is typed unknown[] and sent as-is. The field names inside each object are yours to get right. TypeScript will not catch a misspelled filename.
Inline images
Give an attachment a cid and it becomes part of the message body rather than a file listed at the bottom. Reference it from your HTML with cid: and the same value:
{
"from": "news@mail.acme.com",
"to": "you@yourcompany.com",
"subject": "This month at Acme",
"html": "<p>Hello.</p><img src=\"cid:header-logo\" alt=\"Acme\">",
"attachments": [
{
"filename": "logo.png",
"content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAA...",
"contentType": "image/png",
"cid": "header-logo"
}
]
}
Pick a cid that is unique within the message. Whether the image renders is up to the receiving client, and many hold images until the reader allows them, so keep the alt text meaningful and never put content only in an image.
Size limit
The ceiling is 40 MiB (41,943,040 bytes) on the finished MIME message, measured after encoding. Base64 inflates bytes by about a third, so 40 MiB of MIME is roughly 30 MiB of original file. Budget under that: headers, the HTML and text bodies, and every other attachment share the same 40 MiB.
The limit covers the whole message, not each file, and it applies per message. A batch of 100 gets 40 MiB each, not 40 MiB between them.
Going over does not fail your API call. The 202 has already been returned by the time the message is assembled, so an oversized message fails later in the pipeline: status becomes failed, last_event becomes rejected, and a rejected event lands on the message. Read it back with GET /v1/emails/{id} or watch for it on a webhook. Check your own sizes before sending if you accept user-uploaded files.
Malformed entries are dropped, not rejected
attachments is the one send field that skips schema validation. The API checks that it is an array and stores it; nothing inspects the objects inside until the message is assembled. There is no 422 for a bad attachment.
At assembly, any entry that is not an object with a string filename and a string content is skipped, and the rest of the message goes out without it. A typo in a field name therefore produces a delivered email with a missing file and no error anywhere.
When every entry is dropped, the result is the same as sending no attachments: the message takes the plain path and delivers with the body alone. If there was no body either, no html and no text, nothing is left to send, and status becomes failed with a rejected event, the same as an oversized message.
Attachments elsewhere
- Batch:
POST /v1/emails/batchtakes the same object per entry, with the same rules. - Templates: a template supplies
htmlandtext; attachments stay on the send. Templates cannot carry them. - Test mode: attachments are assembled exactly as in live mode. Only recipients are rewritten. See Test mode.
- Scheduled sends: the attachment is stored at accept and assembled at dispatch, so the 40 MiB check happens when the message goes out, not when you schedule it. See Scheduled sends.
Related
- The
attachmentsfield onPOST /v1/emails - How sending works for the full lifecycle and what
202covers - Errors for the error envelope
