Install
openclaw skills install robotomailGive an AI agent a real inbox and outbound email using Robotomail. Use this skill when the user asks you to send email, check inbox, read messages, reply to email, manage mailboxes, stream inbound events via SSE, set up email webhooks, configure email domains, or perform any email-related task programmatically. Trigger words: email, mailbox, inbox, send email, receive email, mail, webhook email, SSE, email events, agent inbox, outbound email, email infrastructure.
openclaw skills install robotomailRobotomail lets an AI agent send, receive, and react to email programmatically. It is API-first email infrastructure for autonomous agents, assistants, and workflows.
Use Robotomail when you want an agent to:
robotomail.co address or a custom domainGive your agent a real inbox so it can classify, summarize, draft, or reply to incoming support emails.
Let an agent send reminders, nudges, receipts, booking confirmations, or outbound follow-ups from its own mailbox.
Subscribe to inbound email events with webhooks or SSE so your agent can wake up and act immediately.
If the user is new to Robotomail, the fastest path is:
https://robotomail.com or create an account via POST /v1/signupGET /v1/mailboxes or POST /v1/mailboxesPOST /v1/mailboxes/{id}/messagesGET /v1/mailboxes/{id}/messages?direction=INBOUNDNew accounts start inert. A fresh
POST /v1/signupreturns an API key, but the account owner must verify the signup email and add a card (a 3-day Developer trial, no charge until it ends) before the key works — until then product calls (steps 2–5) return402 PAYMENT_REQUIRED. CallPOST /v1/billing/upgradefor a checkout URL and share it with the owner.
All requests require an API key in the Authorization header:
Authorization: Bearer <ROBOTOMAIL_API_KEY>
Base URL: https://api.robotomail.com
If the user doesn't have an API key yet, they can sign up at https://robotomail.com or you can create an account via POST /v1/signup.
| Task | Method | Endpoint |
|---|---|---|
| Send an email | POST | /v1/mailboxes/{id}/messages |
| List inbox messages | GET | /v1/mailboxes/{id}/messages?direction=INBOUND |
| Read a specific message | GET | /v1/mailboxes/{id}/messages/{msgId} |
| List conversation threads | GET | /v1/mailboxes/{id}/threads |
| Read a thread | GET | /v1/mailboxes/{id}/threads/{tid} |
| List mailboxes | GET | /v1/mailboxes |
| Create a mailbox | POST | /v1/mailboxes |
| Add a custom domain | POST | /v1/domains |
| Verify domain DNS | POST | /v1/domains/{id}/verify |
| Set up a webhook | POST | /v1/webhooks |
| Stream events with SSE | GET | /v1/events |
| Upload an attachment | POST | /v1/attachments (multipart) |
| Download an attachment | GET | /v1/attachments/{id} |
| Check account & usage | GET | /v1/account |
For full endpoint details including request/response schemas, read references/api-reference.md.
Use webhooks when Robotomail should push events to your public HTTPS endpoint.
Use SSE when the agent wants a live event stream over a single long-lived connection, for example when running a local listener or agent loop.
If the user says things like "stream inbound mail", "listen for new messages", or "real-time inbox events", prefer SSE.
GET /v1/mailboxes — find the mailbox to send fromPOST /v1/mailboxes/{id}/messages with to, subject, bodyText (and optionally bodyHtml)inReplyTo with the original message's messageId header valueGET /v1/mailboxesGET /v1/mailboxes/{id}/messages?direction=INBOUNDGET /v1/mailboxes/{id}/messages/{msgId}messageId fieldPOST /v1/mailboxes/{id}/messages with inReplyTo set to the original's messageId valuePOST /v1/domains with {"domain": "example.com"}dnsRecords from the response — tell the user to configure these at their DNS providerPOST /v1/domains/{id}/verifyPOST /v1/mailboxes with {"address": "agent", "domainId": "<id>"}POST /v1/webhooks with url and events: ["message.received"]secret from the response, it is shown only onceX-Robotomail-Signature header (HMAC-SHA256 of payload using secret)See references/webhook-verification.md for signature verification code.
GET /v1/eventsmessage.received, message.delivered, message.bounced, and message.complaintPOST /v1/attachments (multipart/form-data, field name file, max 25MB)idattachments: ["<attachment-id>"]Inbound messages with attachments expose them on two surfaces with different shapes. Pick the right one for your access pattern — do not assume REST responses contain ready-to-use download URLs.
message.received payload — data.attachments[] is delivered with a ready-to-use download_url field (presigned R2 URL, valid 24h from publish time) on each attachment, alongside id, filename, content_type, size_bytes, and content_id. Field names are snake_case. Fetch each file directly — no Authorization header needed:
for att in event.data.attachments:
bytes = HTTP_GET(att.download_url)
save_to(att.filename, bytes)
GET /v1/mailboxes/{id}/messages and GET /v1/mailboxes/{id}/messages/{msgId} — attachments[] contains metadata only (id, filename, contentType, sizeBytes, contentId), no URL field. To download, call GET /v1/attachments/{id} for each attachment id and use the url field on that response. Field names are camelCase. This is also how you refresh an expired download_url from an old webhook/SSE replay.Inline images: when an attachment's content_id (snake_case in webhook/SSE) or contentId (camelCase in REST) is non-null, the attachment is referenced in body_html / bodyHtml as <img src="cid:<content_id>">. Rewrite each cid: reference to a real downloadable URL before passing the HTML to a renderer or vision model. From a webhook/SSE payload (where download_url is already on the attachment):
for att in event.data.attachments where att.content_id is not None:
body_html = body_html.replace(f"cid:{att.content_id}", att.download_url)
From a REST message read, fetch a presigned URL per inline attachment first:
for att in message.attachments where att.contentId is not None:
url = GET(f"/v1/attachments/{att.id}").url
bodyHtml = bodyHtml.replace(f"cid:{att.contentId}", url)
Drop limits: if a message has more than 20 attachments, or any single attachment exceeds 25 MB, the payload includes attachments_dropped: true and attachments_dropped_reason: "size" | "count" | "both". The message itself is still delivered, but the over-limit attachments are not stored. Tell the user if you see this flag.
429 if exceededrobotomail.co plus custom domains; Growth and Scale add moreIf a send returns 429, the mailbox has hit its daily/monthly limit or velocity limit. Tell the user and suggest slowing down or upgrading to a higher tier.
All errors return {"error": "message"} with standard HTTP status codes:
401 — Missing or invalid API key403 — Account suspended or scoped key accessing a restricted resource. When suspended, response includes { "suspended": true, "reason": "bounce_rate_exceeded" }. Tell the user to contact support@robotomail.com.404 — Resource not found429 — Rate limit, velocity limit, or send quota exceeded413 — Attachment too largebodyText for the plain-text version. Add bodyHtml only when rich formatting is needed.GET /v1/mailboxes/{id}/threads/{tid}) to see full conversation history before replying.since parameter (ISO-8601) to fetch only recent messages.GET /v1/suppressions) contains bounced/complained addresses — check before sending to addresses that previously failed.