Attachments
Upload and download files attached to email messages
Attachments are files associated with email messages. AgentPost stores attachments in S3-compatible object storage (Cloudflare R2) and provides secure, time-limited access through presigned URLs.
Key properties
| Property | Description |
|---|---|
id | Unique identifier (prefix att_) |
filename | Original file name |
mime_type | MIME content type (e.g., application/pdf) |
size | File size in bytes |
message_id | The message this attachment belongs to |
Upload flow
Uploading an attachment is a two-step process:
- Request a presigned upload URL from the AgentPost API
- Upload the file directly to S3 using the presigned URL
This approach keeps large files off the API server and uploads them directly to object storage.
// Step 1: Get a presigned upload URL
const upload = await client.attachments.getUploadUrl({
filename: 'quarterly-report.pdf',
mime_type: 'application/pdf',
size: 2_450_000, // bytes
});
// Step 2: Upload the file to S3
const fileBuffer = fs.readFileSync('./quarterly-report.pdf');
await fetch(upload.upload_url, {
method: 'PUT',
body: fileBuffer,
headers: {
'Content-Type': 'application/pdf',
},
});
// Step 3: Use the attachment key when sending a message
const message = await client.messages.send('inb_abc123', {
to: [{ email: '[email protected]', name: 'CFO' }],
subject: 'Q1 2026 Report',
text_body: 'Please find the quarterly report attached.',
attachments: [{ key: upload.key, filename: 'quarterly-report.pdf', mime_type: 'application/pdf' }],
});# Step 1: Get a presigned upload URL
upload = client.attachments.get_upload_url(
filename="quarterly-report.pdf",
mime_type="application/pdf",
size=2_450_000,
)
# Step 2: Upload the file to S3
import requests
with open("./quarterly-report.pdf", "rb") as f:
requests.put(
upload.upload_url,
data=f,
headers={"Content-Type": "application/pdf"},
)
# Step 3: Use the attachment key when sending
message = client.messages.send(
inbox_id="inb_abc123",
to=[{"email": "[email protected]", "name": "CFO"}],
subject="Q1 2026 Report",
text_body="Please find the quarterly report attached.",
attachments=[{"key": upload.key, "filename": "quarterly-report.pdf", "mime_type": "application/pdf"}],
)# Step 1: Get a presigned upload URL
UPLOAD=$(curl -s -X POST https://api.agent-post.dev/api/v1/attachments/upload-url \
-H "Authorization: Bearer $AGENTPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename": "quarterly-report.pdf", "mime_type": "application/pdf", "size": 2450000}')
UPLOAD_URL=$(echo $UPLOAD | jq -r '.upload_url')
KEY=$(echo $UPLOAD | jq -r '.key')
# Step 2: Upload the file
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/pdf" \
--data-binary @quarterly-report.pdf
# Step 3: Send with the attachment
curl -X POST https://api.agent-post.dev/api/v1/inboxes/inb_abc123/messages \
-H "Authorization: Bearer $AGENTPOST_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"to\": [{\"email\": \"[email protected]\"}],
\"subject\": \"Q1 2026 Report\",
\"text_body\": \"Please find the quarterly report attached.\",
\"attachments\": [{\"key\": \"$KEY\", \"filename\": \"quarterly-report.pdf\", \"mime_type\": \"application/pdf\"}]
}"Downloading attachments
Download an attachment from a received message:
const attachment = await client.attachments.get('att_xyz789');
// The response includes a presigned download URL
const response = await fetch(attachment.download_url);
const buffer = await response.arrayBuffer();
fs.writeFileSync(`./downloads/${attachment.filename}`, Buffer.from(buffer));attachment = client.attachments.get("att_xyz789")
# Download from the presigned URL
import requests
response = requests.get(attachment.download_url)
with open(f"./downloads/{attachment.filename}", "wb") as f:
f.write(response.content)# Get attachment details including download URL
ATTACHMENT=$(curl -s "https://api.agent-post.dev/api/v1/attachments/att_xyz789" \
-H "Authorization: Bearer $AGENTPOST_API_KEY")
DOWNLOAD_URL=$(echo $ATTACHMENT | jq -r '.download_url')
# Download the file
curl -o "downloaded-file.pdf" "$DOWNLOAD_URL"Size limits
| Constraint | Limit |
|---|---|
| Maximum file size | 10 MB per attachment |
| Maximum attachments per message | No hard limit (subject to total message size) |
Size validation
The size parameter in the upload URL request must match the actual file size. If the uploaded file exceeds the declared size, the upload will be rejected by S3.
Inbound attachments
When your inbox receives an email with attachments, AgentPost automatically:
- Extracts attachments from the email
- Stores them in S3
- Creates attachment records linked to the message
- Makes them available via the
GET /api/v1/attachments/:idendpoint
The message's has_attachments field indicates whether an inbound message has files attached.
Relationships
- Every attachment belongs to one message (
message_id) - Attachments are verified for organization ownership through the message -> inbox chain
- Drafts can reference attachments via
attachments_meta
Tips
- Always use presigned URLs for uploads -- do not try to send file contents through the API directly
- Presigned URLs are time-limited (typically 15 minutes). Generate a new URL if the old one expires.
- The
mime_typeyou declare should match the actual file content for correct handling by email clients - For inbound messages with attachments, check
has_attachmentsbefore making additional API calls - Attachment download URLs are also time-limited. Do not cache them for long-term use.