Send Emails with Attachments Using the Robotomail API

Updated 5 min read

Upload a file, reference its attachment ID in a send request, retrieve incoming files and handle access, size and delivery errors.

John Joubert

John Joubert

Founder, Robotomail

Send Emails with Attachments Using the Robotomail API
Table of contents

To send an attachment with Robotomail, upload the file first, then reference the returned attachment ID in the message request. Your application keeps control of the file, recipients and reason for sending it.

This guide covers outgoing files, incoming attachment retrieval and the failure cases worth testing. See the email API for agents for the complete conversation workflow.

Before you upload

Complete account verification before sending and identify the mailbox that will send the message. Keep your API key on the server. The documented attachment-upload endpoint requires a full-access API key; a mailbox-scoped key cannot perform that upload.

On Free, use your verified signup email address as the recipient, including TEST_RECIPIENT below. Free includes one mailbox, 10 sends and 10 receives per calendar month with that address only. Upgrade to contact other recipients or use a custom domain.

Apply your application's file restrictions before uploading. A filename or MIME type supplied by a user is not proof that the file is safe. If your workflow requires scanning or isolated document conversion, implement those steps explicitly. Do not assume upload acceptance is a malware-scan result.

Check the current attachment-size and account-storage limits in the attachment API and limits reference.

Upload the file

The documented endpoint accepts multipart form data:

curl -X POST https://api.robotomail.com/v1/attachments \
  -H "Authorization: Bearer $ROBOTOMAIL_API_KEY" \
  -F "file=@report.pdf"

Read the returned id. Use that value in the message's attachments array. The API stores the file separately from the email request, so the send request contains an identifier rather than a base64 copy of the file.

Do not substitute a download URL or local file path for that ID. Use the upload behavior documented by Robotomail rather than an assumed presigned-upload route from a different service.

Send the message with the attachment ID

curl -X POST https://api.robotomail.com/v1/mailboxes/MAILBOX_ID/messages \
  -H "Authorization: Bearer $ROBOTOMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["recipient@example.com"],
    "subject": "Your requested report",
    "bodyText": "The report you requested is attached. Reply here with any questions.",
    "attachments": ["ATTACHMENT_ID"]
  }'

Replace both placeholders with actual IDs and use a recipient you control for the first test. bodyText is required; you may also provide bodyHtml. Save the returned message ID and inspect its later delivery outcome.

An agent should propose the intended recipient and file, while your application checks that the recipient may receive it. Avoid giving a model unrestricted access to a directory of customer files just to attach one report.

A JavaScript upload-and-send example

Run this on a server with Node's fetch, FormData and Blob support. It needs a full-access upload key, a mailbox ID and a controlled test recipient in the environment.

import { readFile } from 'node:fs/promises';

const key = process.env.ROBOTOMAIL_API_KEY;
const mailboxId = process.env.ROBOTOMAIL_MAILBOX_ID;
const recipient = process.env.TEST_RECIPIENT;
if (!key || !mailboxId || !recipient) throw new Error('Missing configuration');
const base = 'https://api.robotomail.com/v1';
const headers = { Authorization: `Bearer ${key}` };
const form = new FormData();
form.append('file', new Blob([await readFile('report.pdf')], {
  type: 'application/pdf',
}), 'report.pdf');
const uploaded = await fetch(`${base}/attachments`, {
  method: 'POST', headers, body: form, signal: AbortSignal.timeout(30000),
});
if (!uploaded.ok) throw new Error(`Upload failed: ${uploaded.status}`);
const attachment = await uploaded.json();
const sent = await fetch(`${base}/mailboxes/${encodeURIComponent(mailboxId)}/messages`, {
  method: 'POST',
  headers: { ...headers, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    to: [recipient], subject: 'Test report',
    bodyText: 'The requested report is attached.', attachments: [attachment.id],
  }),
  signal: AbortSignal.timeout(30000),
});
if (!sent.ok) throw new Error(`Send failed: ${sent.status}`);
console.log('Accepted message ID:', (await sent.json()).message.id);

A timeout after submitting the send can leave the result uncertain. Reconcile the message and your application state before blindly retrying and creating a duplicate.

Read an incoming attachment

Retrieve the incoming message and inspect its attachment metadata. Call GET /v1/attachments/ATTACHMENT_ID to get a fresh presigned download URL. The documented URL lifetime is 24 hours; fetch a new one when needed instead of treating it as a permanent link.

Mailbox-scoped keys can access attachments linked to messages in an allowed mailbox. An unattached upload is not automatically available to a scoped key. Account limits and inbound-message visibility can also affect access.

Download only the files the task needs, enforce limits and isolate processing. Incoming inline images can have Content-ID metadata, but that does not establish support for arbitrary outbound CID parameters. See images in email.

Troubleshoot the whole flow

  • Upload rejected: check key scope, multipart encoding, file size and available account storage.
  • Send rejected: check account verification, mailbox access, recipient fields and the returned attachment ID.
  • Download unavailable: check attachment ownership, mailbox scope, inbound limits and URL expiry.
  • File missing in the recipient client: inspect the message and file metadata, then test another controlled client before changing the application.
  • Reply becomes a new conversation: set inReplyTo to the original RFC Message-ID, not the attachment ID or message database UUID.

Connect replies using receive and reply, and keep the agent's file permissions bounded with agent email security.

Give your AI agent a real email address

Create a mailbox, connect your agent and test a conversation. Send, receive and retrieve the thread through one API.

Related posts