Email Attachments Without the Infrastructure Overhead

Upload, attach, send, and retrieve files through a single API. MultiMail handles storage, presigned access, and MIME validation so your agent doesn't have to.


Why this matters

Attachments are a common failure point in agent-driven email systems. Files inflate request payloads, require separate storage backends, and introduce malware vectors that a naive API pass-through won't catch. Most teams bolt on S3, write custom MIME checks, and manually wire presigned URLs — none of which belongs in application logic. When an inbound email carries a .xlsx or a .pdf, the agent needs a safe, structured way to retrieve it without downloading raw bytes into memory or exposing a public URL.


How MultiMail solves this

MultiMail treats attachments as first-class objects. You upload a file to get an attachment ID, reference that ID when sending, and receive structured metadata on inbound emails. Downloads are issued as short-lived presigned URLs — agents never hold raw file bytes, and links expire automatically. MIME type filtering runs at ingestion, so you can allowlist .pdf, .xlsx, and .csv without writing validation code. The result is a consistent read/write pattern for files that works the same whether you're sending a report or parsing an inbound invoice.

1

Upload the attachment

POST the file to /v1/attachments as multipart/form-data. MultiMail stores it, runs MIME validation against your configured allowlist, and returns an attachment ID you reference in subsequent calls.

2

Attach and send

Include the attachment ID in the attachments array of a send_email call. The API links the file to the outbound message without re-uploading bytes. Multiple attachments map to multiple IDs in the same array.

3

Receive attachment events via webhook

Inbound emails with attachments trigger an email.received webhook payload that includes structured attachment metadata: filename, content_type, size_bytes, and attachment_id. No binary data in the webhook body.

4

Fetch with a presigned URL

Call fetch_presigned_url with the attachment ID to get a time-limited download URL. Default expiry is 3600 seconds. Pass the URL to downstream tools, storage systems, or the human approver without granting permanent access.

5

Filter by MIME type

Configure per-mailbox MIME allowlists via the API or dashboard. Attachments whose content_type is not on the list are rejected at ingestion — the email is still delivered, but the attachment is quarantined and flagged in the webhook payload.


Implementation

Upload and send an attachment
python
import multimail

client = multimail.Client(api_key="mm_live_...")

"cm"># Upload the file — returns an attachment object with .id
with open("quarterly-report.pdf", "rb") as f:
    attachment = client.upload_attachment(
        filename="quarterly-report.pdf",
        content_type="application/pdf",
        data=f.read(),
    )

"cm"># Send with the attachment ID
message = client.send_email(
    mailbox="[email protected]",
    to="[email protected]",
    subject="Quarterly report attached",
    body="Hi Dana, I&"cm">#039;ve attached the Q1 report as a PDF. Use the secure link below to download it.",
    attachments=[attachment.id],
)

print(message.id)  "cm"># em_01HXYZ...

Upload a file to get an attachment ID, then reference it in a send_email call.

Upload and send via REST API
bash
"cm"># Step 1: Upload the attachment
ATTACHMENT=$(curl -s -X POST https://api.multimail.dev/v1/attachments \
  -H "Authorization: Bearer $MULTIMAIL_API_KEY..." \
  -F "[email protected]" \
  -F "content_type=application/pdf")

ATTACHMENT_ID=$(echo $ATTACHMENT | jq -r &"cm">#039;.id')
echo "Uploaded: $ATTACHMENT_ID"

"cm"># Step 2: Send the email referencing the attachment ID
curl -s -X POST https://api.multimail.dev/v1/send_email \
  -H "Authorization: Bearer $MULTIMAIL_API_KEY..." \
  -H "Content-Type: application/json" \
  -d "{
    \"mailbox\": \"[email protected]\",
    \"to\": \"[email protected]\",
    \"subject\": \"Quarterly report attached\",
    \"body\": \"Hi Dana, please find the Q1 report attached.\",
    \"attachments\": [\"$ATTACHMENT_ID\"]
  }"

Two-step REST flow: upload the file, then pass the returned attachment ID into send_email.

Receive an attachment and fetch a presigned URL
python
import multimail

client = multimail.Client(api_key="mm_live_...")

"cm"># Read an inbound email (email_id comes from your webhook handler)
email = client.read_email(email_id="em_01HABC...")

for att in email.attachments:
    print(f"{att.filename} — {att.content_type} — {att.size_bytes} bytes")

    "cm"># Presigned URL expires in 1 hour
    download = client.fetch_presigned_url(
        attachment_id=att.id,
        expires_in=3600,
    )
    print(f"Download URL: {download.url}")
    "cm"># Pass download.url to your storage system, approver, or downstream tool

Read an inbound email, iterate its attachments, and get a time-limited download URL for each.

Webhook handler for inbound attachment events
python
from fastapi import FastAPI, Request
import multimail

app = FastAPI()
client = multimail.Client(api_key="mm_live_...")

@app.post("/webhooks/multimail")
async def handle_inbound(request: Request):
    payload = await request.json()

    if payload["event"] != "email.received":
        return {"ok": True}

    email_id = payload["data"]["email_id"]
    attachments = payload["data"].get("attachments", [])

    for att in attachments:
        if att["quarantined"]:
            "cm"># MIME type not on allowlist — skip or alert
            print(f"Quarantined: {att[&"cm">#039;filename']} ({att['content_type']})")
            continue

        url = client.fetch_presigned_url(
            attachment_id=att["id"],
            expires_in=1800,
        )
        # Hand off url.url to your document processing pipeline
        process_document(att["filename"], url.url)

    return {"ok": True}

Parse the email.received webhook payload and extract attachment metadata without downloading file bytes.

Configure a per-mailbox MIME allowlist
python
import multimail

client = multimail.Client(api_key="mm_live_...")

"cm"># Update mailbox to only accept PDFs, spreadsheets, and plain text
client.update_mailbox(
    mailbox_id="mbx_01HDEF...",
    attachment_allowlist=[
        "application/pdf",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        "application/vnd.ms-excel",
        "text/csv",
        "text/plain",
    ],
)

"cm"># Inbound attachments with content_type outside this list
"cm"># are quarantined — webhook payload will include quarantined: true

Restrict which file types a mailbox will accept as attachments. Anything outside the list is quarantined.


What you get

No file bytes in your application layer

Attachments live in MultiMail storage. Your agent works with IDs and presigned URLs — never raw binary payloads in memory or API request bodies.

MIME filtering before your code runs

Configure an allowlist of permitted content types per mailbox. Executables, archives, and other unwanted types are quarantined at ingestion, not after your agent has already processed them.

Consistent send and receive pattern

The same attachment ID model works for both outbound (upload → reference) and inbound (receive metadata → fetch URL). One pattern covers the full file lifecycle.

Time-limited download URLs

Presigned URLs expire on a configurable timer (default 3600s). Downstream tools, human approvers, and external systems get temporary access without permanent storage exposure.

Structured metadata on inbound files

Every inbound attachment comes with filename, content_type, size_bytes, quarantine status, and ID in the webhook payload. Your agent can make routing decisions before ever fetching the file.


Recommended oversight mode

Recommended
monitored
Attachment workflows typically handle business documents — reports, invoices, contracts — where speed matters more than per-action approval. Monitored mode lets the agent send and retrieve files autonomously while giving the human operator a complete activity log. If your use case involves sensitive document categories (legal, financial, health), consider gated_send to require approval before outbound messages with attachments are delivered.

Common questions

What is the maximum attachment size?
Individual attachments are capped at 25 MB, consistent with standard SMTP limits. Total attachment payload per email is capped at 40 MB. For larger files, upload to your own storage and send a presigned URL in the email body instead.
How does MIME type filtering work for inbound email?
When an inbound email arrives, MultiMail inspects the declared content_type of each attachment against your per-mailbox allowlist. Files outside the list are stored but flagged as quarantined: true in the webhook payload and attachment metadata. The email itself is still delivered — only the attachment is blocked from normal retrieval.
Can I send the same attachment to multiple recipients without re-uploading?
Yes. Upload the file once to get an attachment ID, then reference that ID in as many send_email calls as needed. The file is not duplicated in storage for each send.
How long do presigned download URLs remain valid?
The expires_in parameter controls expiry, in seconds. Default is 3600 (one hour). Maximum is 86400 (24 hours). The URL is single-use — once downloaded, subsequent requests will fail even within the validity window, reducing exposure if a URL is forwarded unexpectedly.
Are attachments scanned for malware?
MIME type filtering blocks declared-dangerous content types (e.g., application/x-executable, application/x-msdownload). Full antivirus scanning is available on Pro and Scale plans — attachments are scanned asynchronously and the result appears in the attachment metadata before the presigned URL can be generated.
What happens if a send fails after the attachment is already uploaded?
The attachment object persists independently of the send attempt. You can re-use the same attachment ID in a retry call. Attachment objects expire after 7 days if never linked to a delivered message.
Can I list or search attachments received by a mailbox?
Use read_email to retrieve the full attachment list for a specific message, or check_inbox with include_attachments=true to surface messages that contain files. Direct attachment search by filename across a mailbox is available via the manage_contacts endpoint's sibling attachment index on Pro and Scale plans.

Explore more use cases

The only agent email with a verifiable sender

Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 50-tool MCP server. Formally verified in Lean 4.