← All posts

Send Emails With Attachments: Robotomail API Guide

Send emails with attachments - Programmatically send emails with attachments via Robotomail API. This AI developer guide covers uploads, presigned URLs, and

Send Emails With Attachments: Robotomail API Guide

Your agent generated an invoice. Or a PDF report. Or a screenshot bundle from a support workflow. Sending it by email should be one API call.

Instead, many organizations end up stitching together SMTP credentials, MIME builders, OAuth consent screens, and a mailbox they can’t reliably read from after the send. The attachment goes out, but the reply path is broken. Or the file is too large. Or a provider flags it. Or the agent loses thread context and replies like it has amnesia.

That’s the core problem with trying to send emails with attachments in agent workflows. The send itself is only half the job. Autonomous systems need upload handling, delivery discipline, and a clean way to process replies without a human babysitting the inbox.

Why Sending Attachments is Broken for AI Agents

Human-oriented email tooling assumes a person is sitting in front of a browser. AI agents don’t work that way. They need to create mailboxes programmatically, upload files securely, send outbound messages, and then receive replies in a form the application can act on.

Traditional paths usually fail in one of three ways.

The usual hacks fall apart fast

The first hack is Gmail or Outlook automation. That works for demos, then starts fighting you with consent flows, mailbox ownership assumptions, and brittle app-level setup. It was never designed for autonomous provisioning.

The second hack is raw SMTP. You can make SMTP send a file attachment, but now you own MIME construction, retries, auth failures, bounce handling, and inbound processing. SMTP is transport, not workflow.

The third hack is a one-way transactional API. Those APIs can send, but agent systems also need to receive, correlate, and continue the conversation. If your workflow stops at “message accepted,” your agent isn’t conversational. It’s a fire-and-forget script.

The agent-specific gap

The missing layer is attachment handling that fits an autonomous runtime.

An analysis of developer forum discussions found that 70% of questions about AI email attachments involving presigned URLs or HMAC signing go unanswered, and developers report 40% to 60% failure rates when integrating with frameworks like CrewAI and AutoGen due to rate limits and domain verification issues in practice-oriented discussions around these workflows (Azure Communication Services attachment quickstart context).

That matches what teams run into in production. The problem usually isn’t “how do I attach a PDF?” The problem is:

  • Mailbox lifecycle: create and isolate mailboxes for agents without manual setup
  • File movement: upload binary data safely without exposing public buckets
  • Thread continuity: preserve reply context so the agent knows what it’s answering
  • Verification and trust: avoid mystery webhook traffic and spoofed callbacks

Practical rule: If your email layer can’t receive and thread replies, it’s not an agent email system. It’s an outbound notification tool.

This is why attachment workflows need to be designed around the full loop, not just the send button.

The Robotomail Attachment Workflow Explained

The clean model is simple. Treat the mailbox, the attachment, the outbound message, and the inbound reply as one workflow instead of four unrelated integrations.

A high-level diagram illustrating the Robotomail workflow for sending emails with programmatic attachments and threaded replies.

One mailbox per agent workflow

Start with a real mailbox, not a borrowed sender identity. In an agent system, this matters because the sender address becomes part of memory, routing, and ownership. A support bot, invoice bot, and contract bot shouldn’t all dump replies into the same place.

That mailbox then becomes the anchor for everything else. The attachment upload belongs to that mailbox context. The outbound email references the uploaded file. Inbound replies come back to the same mailbox and keep the conversation intact.

For the conceptual model, the attachment concepts documentation is the useful reference because it frames attachments as objects you upload and reference, not blobs you keep re-encoding into every send request.

Upload first, send second

This is the part most generic tutorials skip. They jump straight into MIME assembly because that’s how older email stacks worked.

For agent workflows, the better pattern is:

  1. Create the mailbox
  2. Upload the file
  3. Get an attachment ID or secure reference
  4. Send the email by referencing that uploaded object
  5. Receive replies through a signed inbound channel
  6. Use threading data to continue the exchange

That architecture has two practical benefits.

First, your send call stays small and predictable. You aren’t shoving large binary payloads through every send operation.

Second, your app gets control points. You can validate, scan, reject, expire, or replace the file before it ever becomes part of a customer-facing message.

Uploading attachments separately is cleaner than building custom MIME payloads in application code. It reduces fragile code paths and makes retries less painful.

Why this shape works better than rolling your own

When teams hand-roll attachment sending, the code usually mixes transport logic with storage logic. That’s where bugs multiply. A retry can create duplicate files. A failed send can leave orphaned storage. A reply can arrive with no thread mapping back to the original task.

A workflow that separates upload from send avoids that tangle. It also maps better to AI systems, where one component generates the file, another approves it, and another sends the message.

Generating Mailboxes and Uploading Files via API

The first production decision is whether your agent gets its own mailbox or shares one with other workflows. In almost every serious system, dedicated mailboxes win. They simplify auditing, inbound routing, and failure isolation.

Then comes attachment upload. That’s where teams often choose the wrong path by attaching raw files directly from app memory every time they send.

A cartoon developer working at a computer, visualizing automated email attachment storage into the cloud via APIs.

Create the mailbox first

A mailbox creation call should be automated and repeatable. Whether you use REST, a CLI, or an SDK, the important point is that mailbox provisioning belongs in your application setup flow, not in a manual dashboard checklist.

A simple cURL shape looks like this:

curl -X POST "https://api.robotomail.com/v1/mailboxes" \
  -H "Authorization: Bearer $ROBOTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "invoice-agent"
  }'

And a Node example follows the same idea:

const res = await fetch("https://api.robotomail.com/v1/mailboxes", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.ROBOTO_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    name: "invoice-agent"
  })
});

const mailbox = await res.json();
console.log(mailbox);

The exact response fields depend on the API version, but the pattern is what matters. Create once, store the mailbox identifier, and treat it as a stable resource in your system.

Scan before upload

Attachments are one of the worst places to cut corners. Corporate filters often sandbox attachments from unknown senders, causing 15-second to 20-minute delays and blocking 30% to 50% outright, and pre-scanning with tools like ClamAV or VirusTotal is a practical step in any programmatic workflow (attachment scanning and privacy guide).

That means your app should scan files before upload, not after a recipient complains.

A minimal local scan step might look like this:

clamscan ./outbound/invoice-1042.pdf

Or from Node:

import { execFile } from "node:child_process";
import { promisify } from "node:util";

const execFileAsync = promisify(execFile);

async function scanWithClamAV(path) {
  await execFileAsync("clamscan", [path]);
}

Don’t treat “generated by our system” as equivalent to “safe.” Agents often transform user content, external files, or model output.

Direct upload for smaller files

For standard documents, direct multipart upload is the straightforward path. The main thing you need back is the attachment identifier that the send request will reference later.

curl -X POST "https://api.robotomail.com/v1/attachments" \
  -H "Authorization: Bearer $ROBOTO_API_KEY" \
  -F "mailbox_id=mbx_123" \
  -F "file=@./outbound/invoice-1042.pdf"

In application code:

import fs from "node:fs";
import FormData from "form-data";

async function uploadAttachment(mailboxId, filePath) {
  const form = new FormData();
  form.append("mailbox_id", mailboxId);
  form.append("file", fs.createReadStream(filePath));

  const res = await fetch("https://api.robotomail.com/v1/attachments", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.ROBOTO_API_KEY}`
    },
    body: form
  });

  return await res.json();
}

The API details for that workflow are covered in the attachments API reference.

Presigned uploads for bigger or client-originated files

Presigned URLs are the better fit when the file originates in the browser, in a worker, or in another service that shouldn’t get full email API credentials. They also help when you want upload and send to be separate steps in your orchestration.

Typical flow:

  • Request an upload target from your backend or email platform
  • Upload the file directly from the producer
  • Store the returned attachment reference
  • Use that reference in the final send call

This pattern keeps binary traffic out of your main application server and reduces the odds that a send retry also re-uploads the file.

A practical rule of thumb works well here:

Approach Good fit Avoid when
Direct upload Server-side generated PDFs, short reports, simple agent jobs The file is large or comes from the client
Presigned upload Browser uploads, larger assets, multi-step workflows You need the fastest possible single-server prototype

If your agent system sends invoices, summaries, screenshots, or generated exports all day, presigned uploads usually age better than direct embedding.

Constructing the API Call to Send Your Email

Once the file is uploaded, the send step should be boring. That’s the goal. If sending an attachment requires custom MIME logic in your app, you’ve already made the workflow harder than it needs to be.

An illustration of a hand pressing a digital email icon alongside an HTTP request JSON code snippet.

Reference attachments by ID, not by raw file content

Message size affects inbox placement much more than many teams expect. Emails over 110 KB face more scrutiny, while emails under 100 KB pass more reliably, and using secure links or references instead of direct embedding can lift inbox placement from the typical 70% to 80% range for attachment-heavy campaigns to over 95% (deliverability impact of attachments).

That’s the practical reason to upload first and send by reference. Your send payload stays compact. Your app also avoids repeated base64 handling and duplicate binary transfer.

A clean cURL request looks like this:

curl -X POST "https://api.robotomail.com/v1/send" \
  -H "Authorization: Bearer $ROBOTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mailbox_id": "mbx_123",
    "from": "invoice-agent@example.com",
    "to": ["customer@example.com"],
    "subject": "Your invoice",
    "html": "<p>Attached is your invoice PDF.</p>",
    "attachments": [
      { "id": "att_abc123" }
    ]
  }'

Multiple attachments follow the same structure:

"attachments": [
  { "id": "att_invoice" },
  { "id": "att_summary" },
  { "id": "att_terms" }
]

Node and Python examples

Here’s the same pattern in Node:

async function sendInvoiceEmail({ mailboxId, from, to, attachmentId }) {
  const res = await fetch("https://api.robotomail.com/v1/send", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.ROBOTO_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      mailbox_id: mailboxId,
      from,
      to: [to],
      subject: "Your invoice",
      html: "<p>Attached is your invoice PDF.</p>",
      attachments: [{ id: attachmentId }]
    })
  });

  return await res.json();
}

And in Python:

import os
import requests

def send_email(mailbox_id, from_email, to_email, attachment_id):
    res = requests.post(
        "https://api.robotomail.com/v1/send",
        headers={
            "Authorization": f"Bearer {os.environ['ROBOTO_API_KEY']}",
            "Content-Type": "application/json",
        },
        json={
            "mailbox_id": mailbox_id,
            "from": from_email,
            "to": [to_email],
            "subject": "Your invoice",
            "html": "<p>Attached is your invoice PDF.</p>",
            "attachments": [{"id": attachment_id}],
        },
    )
    return res.json()

What to validate before you send

Most attachment send bugs come from a short checklist that never ran.

  • Verify recipient formatting: make sure the to field matches what your API expects
  • Check attachment ownership: confirm the uploaded file belongs to the mailbox doing the send
  • Keep HTML lean: heavy markup plus attachments creates size pressure quickly
  • Fail early on missing IDs: don’t let a null attachment reference reach your send queue

This short walkthrough is useful before wiring the endpoint into your app:

Small payloads deliver more reliably. Upload once, reference many times, and keep the final send request focused on message content.

One factual option in this category is Robotomail, which supports attachment uploads and then lets you attach outbound files by reference while also handling inbound mail through webhooks, polling, or server-sent events. For agent systems, that removes the usual split between “sending provider” and “mailbox reader.”

Handling Inbound Replies and Conversation Threading

The outbound email isn’t the end of the workflow. For an AI agent, it’s the start of a conversation. If replies land in a disconnected inbox or arrive without thread context, the agent has no reliable way to continue the exchange.

That’s why thread preservation matters more than fancy send features.

A diagram illustrating the flow of email communication between a customer and a support agent console.

Webhooks are the cleanest inbound path

For most systems, webhooks are the right default. Your app exposes an endpoint, inbound mail events hit it, and your worker decides what to do next. The important part isn’t just receiving the raw email. It’s receiving enough context to map the reply back to the original workflow.

A useful webhook payload usually includes:

{
  "event": "message.received",
  "mailbox_id": "mbx_123",
  "message_id": "msg_reply_456",
  "thread_id": "thr_789",
  "from": "customer@example.com",
  "subject": "Re: Your invoice",
  "text": "Thanks, I have a question about line 3.",
  "attachments": []
}

With that structure, your agent doesn’t need to guess whether the reply belongs to invoice run A or support ticket B. The thread identifier does the routing.

Threading is what makes the agent usable

Email clients already use headers like In-Reply-To and References to preserve conversation chains. Your infrastructure should surface that threading in a form your application can trust and use.

Without that, teams usually end up doing one of two bad things:

  1. Parsing subject lines and hoping “Re:” plus a token is enough
  2. Searching recent outbound mail and guessing which message a reply belongs to

Both approaches break under load, especially once humans forward, trim, or edit messages.

A reply pipeline without thread context forces your agent to reconstruct state from scraps. That’s expensive and unreliable.

There’s another benefit. A developer pain point in attachment workflows is post-send control. By using reply webhooks, developers can detect active conversations and build logic like revoking a presigned attachment URL after a reply arrives, which is a capability most tutorials skip (secure attachment control discussion).

Attachments don’t end when the email is sent

This matters in contract, approval, and support flows where documents evolve inside an email chain. If your agent is coordinating feedback on a file, it helps to think beyond “attach and forget” and into the broader workflow of managing documents in email chains with attachments, especially when versioning and review state need to stay visible.

If webhooks aren’t your preferred inbound model, polling and server-sent events can still work. The important part is consistency. Pick one delivery model, make threading first-class, and keep replies tied to mailbox identity from the start.

Security, Rate Limits, and Troubleshooting

Attachment workflows fail in predictable ways. Most of them come from treating email as a thin send API instead of a security-sensitive transport with reputation consequences.

The risk profile is not theoretical. 94% of malware is delivered via email attachments according to the 2024 Verizon DBIR, which is why built-in protections like automatic DKIM, SPF, DMARC, and HMAC-signed webhooks matter in autonomous systems (Verizon DBIR chart summary).

Security checks that belong in every deployment

If your agent can send files, your system needs guardrails around both outbound and inbound handling.

  • Verify webhook signatures: reject inbound events that fail HMAC verification
  • Scan files before upload: don’t rely on recipient-side filtering to catch malicious content
  • Use suppression lists: stop retrying bad recipients and protect sender reputation
  • Authenticate your sending domain: proper DKIM, SPF, and DMARC aren’t optional for production mail

For teams building broader trust reviews around agent systems, it also helps to look at external references on general security considerations so the email layer sits inside a real security posture instead of being treated as a side feature.

Common failures and what usually causes them

A compact troubleshooting table is more useful than a long theory section.

Symptom Likely cause Practical fix
413 Payload Too Large You tried to send too much binary data in one request Upload first, then reference the attachment in the send payload
Attachment bounces or missing files File type, size, or recipient-side policy issue Prefer common document types and reduce payload size
Reply arrives but agent loses context No thread mapping in your app Persist message and thread IDs when sending
Webhook events look suspicious Signature not checked Require HMAC verification before processing
Deliverability drops after repeated campaigns Too many heavy attachments or poor sender hygiene Shift bulky files to secure links and monitor suppression behavior

Rate limits are a feature, not a nuisance

Autonomous systems can send bad traffic very quickly. A loop bug, a retry storm, or a misconfigured agent can trash a sender’s reputation before a human notices.

That’s why per-mailbox rate limiting and quotas are healthy constraints. The product information available for Robotomail states that the free tier includes one mailbox with 50 sends per day and 1,000 monthly sends, while higher plans add more mailboxes and higher limits. Those controls are useful because they contain damage when an agent behaves badly.

Storage quotas matter too. Attachments accumulate fast, especially if your workflows generate reports, screenshots, or exported documents. Put cleanup policies in place early.

Your email system should assume the agent will eventually make a mistake. Good limits keep that mistake contained.

The mature approach is simple. Scan before upload. Authenticate the domain. verify webhook signatures. keep attachment handling separate from send logic. log enough metadata to trace every message, file, and reply.

That setup is less glamorous than a demo video, but it’s what holds up when the agent is sending real customer documents every day.


If you’re building autonomous workflows and need real mailboxes, attachment uploads, outbound sending, and inbound reply handling in one API, Robotomail is worth evaluating. It’s built for agent-native email flows, including mailbox creation, secure attachment handling, and threaded replies without the usual SMTP or OAuth plumbing.