# Attachments

> Attach files to API emails as base64 content or from a URL, embed inline images with Content-ID, and stay within allowed file types and size limits.

This page shows how to attach files to emails you send with `POST /emails`, either as base64-encoded content or by letting Emailit download them from a URL. It also covers inline images, allowed file types and size limits.

## Attachment fields

Pass an `attachments` array. Each item is an object with these fields:

- `filename` (string, required): File name shown to the recipient. It must end in an [allowed extension](#allowed-file-types).
- `content` (string): The file encoded as base64. Use either `content` or `url`, not both.
- `url` (string): An `http://` or `https://` URL that Emailit downloads the file from. Use either `content` or `url`, not both.
- `content_type` (string): The MIME type, such as `application/pdf`. Required with `content`. With `url`, it defaults to the `Content-Type` the server returns.
- `content_id` (string): A Content-ID. Setting it makes the attachment inline, so your HTML can show it with `cid:`.
- `encoding` (string): How `content` is encoded. Leave it as `base64` unless you have a reason to change it.

## Attach a file as base64

Read the file, encode it as base64, and send it with its MIME type.

**cURL**

```bash
curl https://api.emailit.com/v2/emails \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"from\": \"Acme Billing <billing@acme.com>\",
    \"to\": \"ada@example.com\",
    \"subject\": \"Invoice INV-1042\",
    \"text\": \"Your invoice is attached.\",
    \"attachments\": [{
      \"filename\": \"INV-1042.pdf\",
      \"content\": \"$(base64 < INV-1042.pdf | tr -d '\n')\",
      \"content_type\": \"application/pdf\"
    }]
  }"
```

**Node.js**

```javascript
import { readFile } from 'node:fs/promises';
import { Emailit } from '@emailit/node';

const emailit = new Emailit(process.env.EMAILIT_API_KEY);
const pdf = await readFile('INV-1042.pdf');

await emailit.emails.send({
  from: 'Acme Billing <billing@acme.com>',
  to: 'ada@example.com',
  subject: 'Invoice INV-1042',
  text: 'Your invoice is attached.',
  attachments: [
    {
      filename: 'INV-1042.pdf',
      content: pdf.toString('base64'),
      content_type: 'application/pdf',
    },
  ],
});
```

**Python**

```python
import base64
import os
from emailit import EmailitClient

client = EmailitClient(os.environ["EMAILIT_API_KEY"])

with open("INV-1042.pdf", "rb") as f:
    pdf = base64.b64encode(f.read()).decode("ascii")

client.emails.send({
    "from": "Acme Billing <billing@acme.com>",
    "to": "ada@example.com",
    "subject": "Invoice INV-1042",
    "text": "Your invoice is attached.",
    "attachments": [{
        "filename": "INV-1042.pdf",
        "content": pdf,
        "content_type": "application/pdf",
    }],
})
```

**PHP**

```php
$emailit = Emailit::client(getenv('EMAILIT_API_KEY'));

$emailit->emails()->send([
    'from' => 'Acme Billing <billing@acme.com>',
    'to' => 'ada@example.com',
    'subject' => 'Invoice INV-1042',
    'text' => 'Your invoice is attached.',
    'attachments' => [[
        'filename' => 'INV-1042.pdf',
        'content' => base64_encode(file_get_contents('INV-1042.pdf')),
        'content_type' => 'application/pdf',
    ]],
]);
```

## Attach a file from a URL

Instead of encoding the file yourself, give Emailit a `url`. Emailit downloads the file while it builds the message, so the request takes as long as the download.

```json
{
  "from": "Acme Billing <billing@acme.com>",
  "to": "ada@example.com",
  "subject": "Invoice INV-1042",
  "text": "Your invoice is attached.",
  "attachments": [
    {
      "filename": "INV-1042.pdf",
      "url": "https://files.acme.com/invoices/INV-1042.pdf"
    }
  ]
}
```

The URL has to meet these rules, or the request fails with `422` and `Attachment error`:

- It uses `http` or `https` and points to a public host. Private and internal addresses are refused.
- It returns the file directly with a `2xx` status. Redirects aren't followed.
- The download finishes within 30 seconds.
- The file is no larger than 25 MB, as reported by its `Content-Length` header.

Emailit builds a separate copy of the message for each recipient and downloads URL attachments for each copy. Make sure signed or expiring URLs stay valid for the whole request, and that the file host can handle one download per recipient.

## Embed inline images

To show an image inside the HTML body instead of as a separate attachment, give it a `content_id` and reference that ID with `cid:` in an `img` tag.

```json
{
  "from": "Acme <hello@acme.com>",
  "to": "ada@example.com",
  "subject": "Your weekly report",
  "html": "<p><img src=\"cid:chart-week-40\" alt=\"Weekly signups\" width=\"600\"></p>",
  "attachments": [
    {
      "filename": "chart.png",
      "content": "iVBORw0KGgoAAAANSUhEUgAA...",
      "content_type": "image/png",
      "content_id": "chart-week-40"
    }
  ]
}
```

The `content_id` in the attachment and the value after `cid:` must match exactly. Inline images make the message larger for every recipient, so for logos and other shared images, a hosted image URL in the HTML is usually the better choice.

## Size limits

| Limit | Value |
| --- | --- |
| Whole message, after encoding | 40 MB. Larger messages fail with `413 Message too large`. |
| One attachment downloaded from `url` | 25 MB |
| Download timeout for `url` | 30 seconds |
| JSON request body | 50 MB |

Base64 encoding makes files about a third larger, and the 40 MB limit applies to the encoded message. In practice, keep the combined size of your files under about 29 MB. For anything bigger, upload the file to your own storage and send a link.

## Allowed file types

`filename` must end in one of these extensions. Any other extension, or a name without an extension, fails validation with `400`.

| Category | Extensions |
| --- | --- |
| Text | `.txt`, `.csv`, `.log`, `.css`, `.ics`, `.xml` |
| Images | `.jpg`, `.jpe`, `.jpeg`, `.gif`, `.png`, `.bmp`, `.psd`, `.tif`, `.tiff`, `.svg`, `.indd`, `.ai`, `.eps` |
| Documents | `.doc`, `.docx`, `.rtf`, `.odt`, `.ott`, `.pdf`, `.pub`, `.pages`, `.mobi`, `.epub` |
| Audio | `.mp3`, `.m4a`, `.m4v`, `.wma`, `.ogg`, `.flac`, `.wav`, `.aif`, `.aifc`, `.aiff` |
| Video | `.mp4`, `.mov`, `.avi`, `.mkv`, `.mpeg`, `.mpg`, `.wmv` |
| Spreadsheets | `.xls`, `.xlsx`, `.ods`, `.numbers` |
| Presentations | `.odp`, `.ppt`, `.pptx`, `.pps`, `.key` |
| Archives and contacts | `.zip`, `.vcf` |
| Email | `.eml` |
| Signatures and encryption | `.p7c`, `.p7m`, `.p7s`, `.pgp`, `.asc`, `.sig` |

Executable files and scripts aren't on the list and can't be attached.

## Read the attachments of a sent email

[List attachments](/docs/api-reference/emails/attachments/) (`GET /emails/{id}/attachments`) returns each attachment with its `filename`, `content_type`, `size`, `content_id`, `content_disposition` (`attachment` or `inline`) and base64 `content`. It needs a **Full Access** key. Attachments are deleted together with the message contents when your [data retention](/docs/data-retention/) period ends.

## Troubleshooting

| Error | What to check |
| --- | --- |
| `Attachment at index 0 missing content_type (required when using 'content')` | Add `content_type` to every base64 attachment. |
| `Attachment 'report.exe' has unsupported file type '.exe'` | Use an allowed extension, or put the file in a `.zip`. |
| `Attachment at index 0 cannot have both 'content' and 'url'` | Send one of the two. |
| `Attachment error` with `Failed to fetch attachment` | The URL returned an error status, redirected, timed out or isn't public. Open it from a server outside your network to check. |
| `Attachment error` with `Attachment too large (max 25MB)` | Host the file and send a link instead. |
| `413 Message too large` | Reduce the total size of attachments. |

## Related

- [Send an email](/docs/email-api/send-email/)
- [Send an email](/docs/api-reference/emails/send/) API reference
- [Data retention](/docs/data-retention/)

---
Source: https://emailit.com/docs/email-api/attachments/
