PolpoMail
Developers

Polpo Mail API

Read, organise and send email from your own code with a simple JSON API, or connect any IMAP/SMTP library. Everything authenticates with personal access tokens.

Access tokens

Create tokens in Settings › Access tokens. Each token has a name, an optional expiry date and one or more permissions:

ScopeAllows
readIMAP login; GET mailboxes, messages, raw messages and attachments
sendSMTP submission; POST /api/v1/messages/send
modifyPATCH and DELETE messages (flags, move, trash)

Tokens start with pm_, are shown only once, and can be revoked at any time. Your account password is never accepted by the API, SMTP or IMAP; it only works on the website, where two-step verification protects it.

SMTP & IMAP

Use your email address as the username and a token as the password:

SMTP
mx.polpomail.com, port 587 (STARTTLS), AUTH PLAIN, token with send
IMAP
mx.polpomail.com, 143 (STARTTLS), token with read
# Python: send with smtplib
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["From"], msg["To"], msg["Subject"] = "you@yourcompany.com", "client@example.com", "Hello"
msg.set_content("Sent with an access token.")
with smtplib.SMTP("mx.polpomail.com", 587) as s:
    s.starttls()
    s.login("you@yourcompany.com", "pm_…")
    s.send_message(msg)

API basics

Account

GET /api/v1/me: who the token belongs to, storage usage and the token's scopes. Works with any scope.

curl https://polpomail.com/api/v1/me -H "Authorization: Bearer $TOKEN"

{"email":"you@yourcompany.com","name":"You","domain":"yourcompany.com","role":"admin",
 "quota_bytes":1073741824,"used_bytes":5242880,
 "token":{"name":"CRM","scopes":["read","send"],"expires_at":null}}

Mailboxes

GET /api/v1/mailboxes (scope read)

{"mailboxes":[{"name":"INBOX","role":"","messages":42,"unread":3},
              {"name":"Sent","role":"sent","messages":17,"unread":0}, …]}

List messages

GET /api/v1/messages (scope read). Newest first.

ParameterDefaultMeaning
mailboxINBOXMailbox name
qSearch subject, sender and recipients
unreadfalsetrue returns only unread messages from the page
limit / offset50 / 0Paging (limit up to 100); use next_offset from the response
curl "https://polpomail.com/api/v1/messages?mailbox=INBOX&limit=2" -H "Authorization: Bearer $TOKEN"

{"mailbox":"INBOX","total":42,"offset":0,"limit":2,"next_offset":2,
 "messages":[{"id":1187,"mailbox":"INBOX","uid":42,"date":"2026-11-07T09:42:00Z",
   "from":"Ana Ruiz <ana@example.com>","to":"you@yourcompany.com","subject":"Q4 roadmap",
   "size":18342,"flags":[],"seen":false,"flagged":false,"answered":false}, …]}

Read a message

GET /api/v1/messages/{id} returns headers, the decoded text and html bodies and a list of attachments. Add ?mark_seen=true to mark it read (needs modify too).

GET /api/v1/messages/{id}/raw returns the original RFC 5322 message (message/rfc822).

GET /api/v1/messages/{id}/attachments/{index} downloads one attachment; the url of each is included in the message response.

Organise messages

PATCH /api/v1/messages/{id} (scope modify). Any of seen, flagged and answered (booleans), and mailbox to move it.

curl -X PATCH https://polpomail.com/api/v1/messages/1187 -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" -d '{"seen":true,"mailbox":"Archive"}'

DELETE /api/v1/messages/{id} moves the message to Trash; when it is already in Trash, or with ?permanent=true, it is deleted for good.

Send a message

POST /api/v1/messages/send (scope send). The message is DKIM-signed, delivered and saved to Sent (unless "save_sent": false).

curl https://polpomail.com/api/v1/messages/send -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" -d '{
  "to": ["Client <client@example.com>"],
  "cc": [], "bcc": [],
  "subject": "Your invoice",
  "text": "Hi, please find the invoice attached.",
  "html": "<p>Hi, please find the invoice attached.</p>",
  "attachments": [{"filename": "invoice.pdf", "content_type": "application/pdf", "content": "JVBERi0xLjQK…"}]
}'

HTTP/1.1 202 Accepted
{"message_id":"<3f9c…@yourcompany.com>","status":"sent","recipients":1}

Optional fields: from (your address or an alias you belong to), reply_to, in_reply_to and references for threading. Attachment content is standard base64. At most 100 recipients, and the whole message up to 25 MB. If some recipients fail, status is partial and errors lists them.

Errors & limits

HTTP/1.1 403 Forbidden
{"error":{"code":"insufficient_scope","message":"This token needs the \"send\" scope."}}
StatusCodes
400 / 422bad_json, invalid_recipient, no_recipients, empty_body, bad_attachment
401unauthorized, invalid_token (missing, wrong, expired or revoked)
403insufficient_scope, from_not_allowed, account_unavailable
404message_not_found, mailbox_not_found, attachment_not_found, not_found
413too_large
429rate_limited (600 requests per minute per token), send_limit (500 external recipients per user per hour), throttled; see Retry-After

Need higher limits, webhooks or a dedicated deployment? Talk to sales.