Storage & Attachments
How storage is tracked, attachment handling, plan limits, and retention windows.
Molted Email tracks storage usage per tenant and stores attachments in secure object storage.
Storage model
Storage is split into two categories:
- Message bodies -- the text and HTML content of inbound messages, stored with your tenant data.
- Attachments -- binary files in secure object storage, with metadata tracked alongside.
Both categories count toward your plan's storage limit.
Plan limits
| Plan | Storage limit | Retention |
|---|---|---|
| Trial (and a paid plan during its 3-day trial) | 50 MB | 7 days |
| Free | 100 MB | 14 days |
| Solo (and legacy Starter) | 3 GB | 30 days |
| Team (and legacy Growth) | 10 GB | 90 days |
| Enterprise | Unlimited | 365 days |
When your account is in the expired state it keeps its previous storage limit, and its retention becomes the previous retention plus 30 days, so nothing is purged the moment a plan lapses. Uploads are refused (see Uploads on an expired plan), but attachments of email you receive are still stored.
Retention windows
Attachments older than your plan's retention window are automatically cleaned up daily, up to 1,000 per tenant per run:
- The file is removed from storage.
- The attachment row is marked
deleted. - Storage usage counters are decremented.
Attachments of a message held for the inbound allowance are never expired while it is held; once it's released, retention counts from the release.
The plan's retention window above covers attachment files only. Message text is not removed by it.
Message content retention
There is a second, separate daily pass that clears message content, but it only runs for a workspace that has a content retention period set. For such a workspace it empties, for every message older than the period, the bodies (plain text and encrypted), the raw inbound message, the stored send payloads and the humanizer's before and after text. Subjects, addresses, timestamps and delivery status are kept, so threads and lists still render, and the body search index entries for those messages are deleted with them.
No plan sets a content retention period, and nothing you can reach sets one: not the portal, the API, the CLI or MCP. So today, message text is kept until you delete it yourself. To remove it, use Deleting message content for everything, or Deleting contacts for one person's messages.
Checking storage usage
Storage usage is included in the billing status endpoint:
curl https://mailbox.molted.email/v1/billing/status \
-H "Authorization: Bearer YOUR_API_KEY"The response includes a storage field:
{
"storage": {
"messageBodyBytes": 1048576,
"attachmentBytes": 5242880,
"totalBytes": 6291456,
"limitBytes": 3221225472,
"retentionDays": 30
}
}A null limitBytes means unlimited storage (Enterprise plan). A signed-in human sees the same numbers at GET /v1/me/storage, which returns { storage: { messageBodyBytes, attachmentBytes, totalBytes, limitBytes, remaining, retentionDays }, mailboxes: { used, limit } }.
Attachment API
Upload an attachment
POST https://mailbox.molted.email/v1/attachmentsmolted attachments upload ./invoice.pdf --mailbox MAILBOX_IDcurl -X POST https://mailbox.molted.email/v1/attachments \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filename": "invoice.pdf",
"contentType": "application/pdf",
"contentBase64": "JVBERi0xLjQK..."
}'Body: filename, contentType, contentBase64 (all required) and mailboxId (optional UUID). The decoded file can be at most 10 MB (400 otherwise). Executable and script extensions are refused with 400: .exe .bat .cmd .scr .pif .com .vbs .vbe .js .jse .wsf .wsh .msi .msp .cpl .hta .inf .reg .ps1 .lnk .dll .sys. The content type is sniffed from the file's bytes; when it differs from what you declared, your value is kept in contentTypeDeclared. A mailbox-scoped key needs send on the mailbox.
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"filename": "invoice.pdf",
"contentType": "application/pdf",
"contentTypeDeclared": null,
"sizeBytes": 204800,
"checksumSha256": "...",
"status": "uploaded"
}Pass the returned id in a send's attachments array (at most 20 per send). An attachment that hasn't finished its scan is refused with 400 Attachment(s) not yet scanned. There is no MCP upload tool.
List attachments for a message
molted attachments list --message-id MSG_ID --message-type inboundcurl "https://mailbox.molted.email/v1/attachments?messageId=MSG_ID&messageType=inbound" \
-H "Authorization: Bearer YOUR_API_KEY"messageType is inbound or thread (anything else is a 400); a non-UUID messageId returns []. Attachments of a message that is held, still pending classification, or quarantined are left out (quarantined ones are listed for a key with the operator scope). In MCP, a thread's attachments come with get_thread.
Response
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"filename": "invoice.pdf",
"contentType": "application/pdf",
"sizeBytes": 204800,
"status": "uploaded",
"createdAt": "2026-03-01T12:00:00Z"
}
]Download an attachment
molted attachments download ATTACHMENT_ID --output invoice.pdfcurl "https://mailbox.molted.email/v1/attachments/ATTACHMENT_ID/download" \
-H "Authorization: Bearer YOUR_API_KEY"MCP: get_attachment (attachment_id).
Returns a presigned URL valid for 1 hour, plus the attachment's own metadata (added alongside url, so an
existing integration that reads only url is unaffected):
{
"url": "https://storage.molted.email/tenants/tenant-abc/attachments/2026/03/550e8400...?X-Amz-Signature=...",
"expiresInSeconds": 3600,
"filename": "invoice.pdf",
"contentType": "application/pdf",
"sizeBytes": 204800,
"status": "uploaded"
}filename comes verbatim from the sender's email on an inbound attachment: treat it as untrusted, third-party
data, not as a trusted file path. status: "uploaded" means the file scan has not finished yet (still
downloadable); "scan_passed" means it passed the file scan, which is a file-type check, not antivirus. The statuses are pending, uploaded, scan_passed, scan_failed and deleted; a deleted or scan_failed attachment is a 404. A 403 carries pending_classification, inbound_quota_held or quarantined_requires_operator as its message when the message it belongs to isn't readable yet.
A signed-in human downloads through GET /v1/me/attachments/:id/download, which streams the file, never serves a quarantined attachment, and is a 404 for an archived mailbox.
What the file scan checks
Every attachment, uploaded by an agent or received on an inbound email, goes through two checks.
- Extension. The executable and script extensions listed under Upload an attachment are refused. An upload gets a
400; a received attachment with one of them is dropped on arrival (the message itself is kept). - File bytes. Once stored, the file's first bytes are compared with known executable and binary formats: Windows executables and DLLs, Linux (ELF) and macOS (Mach-O) binaries, Java class files, Windows shortcuts, and OLE compound files (legacy
.docand.xls,.msiinstallers). A match setsscan_failed: the file is never served or sent, whatever it is named.
It is a file-type check, not an antivirus engine. It does not look for malware in other formats (PDFs, images, modern Office files, HTML or SVG files), and it does not unpack archives such as .zip. It does not read a file's text either: the safety checks screen the subject and body, so instructions hidden inside an attachment are not detected.
What happens at the storage limit
When you exceed your plan's storage limit, attachment uploads are rejected with a 403 Forbidden response:
{
"statusCode": 403,
"message": "Storage limit exceeded. Used: 3221225472 bytes, limit: 3221225472 bytes.",
"error": "Forbidden"
}An inbound attachment that would pass the limit is dropped (and logged), while the message itself is kept. Existing attachments remain accessible. To free up space, wait for the retention cleanup or upgrade your plan.
Uploads on an expired plan
Once a plan has ended (or a failed payment's grace period ran out), uploads are refused with 402 Payment Required:
{
"error": "subscription_expired",
"message": "Your subscription has expired. Reactivate a plan to upload attachments."
}Listing and downloading attachments keep working, so you can export your data. Attachments of email you receive are still stored. A new account that hasn't activated a plan yet can upload.