REST API (Send Email)
This guide explains how to send messages and trigger tracking using MonkeysMail’s public API. It covers authentication, scopes, request/response formats, error handling, quotas, and examples in cURL, Node.js, PHP, and Python.
1) Authentication
Send your API key in the header:
- Format:
prefix.secret
- Scopes: required per endpoint (see below). Accepted aliases & wildcards are supported, e.g.
mail
,messages
,mail:*
,messages.*
, or*
. - Binding: Keys are bound to a Company and a Domain. Requests using keys without a company/domain will be rejected (
403
).
Required scopes per endpoint
Endpoint | Scope |
---|---|
POST /messages/send | mail:send |
POST /messages/send/list | mail:send:list |
POST /messages/send/segment | mail:send:segment |
The platform also accepts scope aliases (
messages:*
, etc.). If a key lacks the scope you’ll receive403 forbidden
with{ required: "..." }
.
2) Sending: One‑off (explicit recipients)
Endpoint: POST smtp.monkeysmail.com/messages/send
Query/body mode:
- Default is enqueue (asynchronous queue).
- To force immediate send, pass
mode=sync
as a query param or{ "mode": "sync" }
in JSON body or{ "runNow": true }
.
Request body
{
"from": { "email": "no-reply@example.com", "name": "Example Inc." },
"to": ["user1@example.com", "user2@example.com"],
"cc": ["cc@example.com"],
"bcc": ["audit@example.com"],
"subject": "Welcome to Example",
"text": "Hello! This is the text part.",
"html": "<h1>Hello!</h1><p>This is the HTML part.</p>",
"reply_to": "support@example.com",
"headers": { "X-Campaign": "onboarding" },
"tags": ["onboarding", "welcome"],
"metadata": { "userId": "u_123", "plan": "pro" },
"template_id": "tpl_welcome_v3",
"variables": { "firstName": "Jane" },
"attachments": [
{
"filename": "terms.pdf",
"content": "<base64>",
"content_type": "application/pdf"
}
]
}
Notes
- At least one of
to
/cc
/bcc
must contain a valid email (duplicates are deduped case‑insensitively). attachments[].content
must be base64.headers
are forwarded as custom message headers (prefix withX-
when appropriate).metadata
is stored for search & webhooks/exports (if enabled).- If you use templates, include
template_id
andvariables
.
Responses
Enqueue (default)
{
"mode": "enqueue",
"status": "queued",
"recipients": 2,
"message": "queued"
}
Status: 202 Accepted
(or 200
, 201
, 503
depending on internal state). Possible status
values: queued
, preview
, queue_failed
, sent
.
Sync (immediate)
{
"mode": "sync",
"company": 42,
"domain": 7,
"at": "2025-09-23T22:10:00+00:00",
"id": "msg_...",
"status": "sent",
"accepted": ["user1@example.com"],
"rejected": []
}
Status mirrors delivery attempt (commonly 200
/201
).
Validation error (example)
{
"error": "invalid_request",
"message": "At least one recipient (to/cc/bcc) is required",
"field": "to/cc/bcc"
}
3) Sending to a List (server‑side recipient resolution)
Endpoint: POST smtp.monkeysmail.com/messages/send/list
Provide a list hash (64‑char hex, typically sha256
) in the JSON body.
Request
{
"listHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"from": { "email": "campaigns@example.com", "name": "Example" },
"subject": "September newsletter",
"html": "<html>…</html>",
"text": "…",
"tags": ["newsletter"],
"metadata": { "edition": "2025-09" }
}
Response
{
"status": "queued",
"target": { "type": "list", "hash": "aaaaaaaa…" },
"recipientsTotal": 1200,
"recipientsQueued": 1200,
"recipientsFailed": 0,
"details": [{ "to": "alice@example.com", "status": "queued" }],
"detailsTruncated": true
}
- Status is
202
when everything queued; if mixed results you may get207 Multi-Status
with partial failures.
4) Sending to a Segment (server‑side recipient resolution)
Endpoint: POST smtp.monkeysmail.com/messages/send/segment
Provide a segment hash (64‑char hex) in the JSON body.
Request
{
"segmentHash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"from": { "email": "offers@example.com", "name": "Example" },
"subject": "VIP Offer",
"html": "<html>…</html>",
"text": "…"
}
Response
{
"status": "queued",
"recipients": 347,
"target": { "type": "segment", "hash": "bbbbbbbb…" },
"message": "queued"
}
5) Tracking
These endpoints record events related to a unique RID (recipient token) embedded by MonkeysMail.
Open tracking (pixel)
- GET/HEAD
smtp.monkeysmail.com/t/o/{rid}.gif
- Returns a 1×1 GIF. HEAD also records an open (useful for privacy‑preserving proxies).
Example <img>
tag in HTML:
<img src="smtp.monkeysmail.com/t/o/${RID}.gif" width="1" height="1" alt="" style="display:none" />
Click tracking (redirect)
- GET
smtp.monkeysmail.com/t/c/{rid}?u={b64url(target)}
u
is the URL‑safe base64 of the final destination.
Example link rewrite:
<a href="smtp.monkeysmail.com/t/c/${RID}?u=${B64URL('https://example.com/pricing')}">Pricing</a>
Unsubscribe handler
- GET
smtp.monkeysmail.com/t/u/{rid}?reason={text}
- Records an unsubscribe (or
reason
) and returns a simple HTML confirmation page. You can customize your templates to point the unsubscribe footer here.
6) Quotas & Rate Limiting
MonkeysMail enforces message quotas per plan and time window:
- Starter: window = day, limit = 150 message units/day
- Higher tiers: window = month, limit =
plan.includedMessages
(or unlimited)
When a request exceeds the remaining allowance you’ll get HTTP 429:
{
"error": "rate_limited",
"reason": "Message quota exceeded for your plan",
"window": "day",
"limit": 150,
"remaining": 0,
"resetAt": "2025-09-24T00:00:00+00:00"
}
Unit accounting: The API counts unique valid recipient emails across to
/cc
/bcc
in a request or the resolved set for lists/segments.
7) Common Errors
HTTP | error | message | Notes |
401 | unauthorized | Invalid or missing API key | Missing/invalid X-API-Key |
403 | forbidden | API key is missing the required scope | Includes { required: "…" } |
422 | invalid_request | Field‑specific reason | e.g., no recipients |
429 | rate_limited | Quota exceeded | See payload above |
500 | internal_error | Internal Server Error | Log correlation on server |
8) Code Examples
cURL (one‑off send, enqueue)
curl -X POST "smtp.monkeysmail.com/messages/send" \
-H "Content-Type: application/json" \
-H "X-API-Key: pk_live_abc123.7f9e..." \
-d '{
"from": {"email": "no-reply@example.com", "name": "Example"},
"to": ["user@example.com"],
"subject": "Hello",
"text": "Hi there",
"html": "<p>Hi there</p>",
"tags": ["hello"]
}'
Node (fetch) – sync send
import fetch from "node-fetch";
const res = await fetch(`${process.env.API_BASE}/messages/send?mode=sync`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.MONKEYSMAIL_API_KEY
},
body: JSON.stringify({
from: { email: "no-reply@example.com", name: "Example" },
to: ["user@example.com"],
subject: "Hello",
html: "<p>Hi</p>",
})
});
const data = await res.json();
console.log(res.status, data);
PHP (cURL) – list send
$body = [
'listHash' => 'aaaaaaaa...64hex...',
'from' => ['email' => 'campaigns@example.com', 'name' => 'Example'],
'subject' => 'September',
'html' => '<h1>Hi</h1>'
];
$ch = curl_init("smtp.monkeysmail.com/messages/send/list");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . getenv('MONKEYSMAIL_API_KEY')
],
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_RETURNTRANSFER => true,
]);
$out = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
Python (requests) – segment send
import os, base64, requests
API_BASE = os.environ.get('API_BASE')
KEY = os.environ.get('MONKEYSMAIL_API_KEY')
payload = {
"segmentHash": "bbbbbbbb...64hex...",
"from": {"email": "offers@example.com", "name": "Example"},
"subject": "VIP Offer",
"html": "<p>Hi VIP</p>"
}
res = requests.post(f"smtp.monkeysmail.com/messages/send/segment", json=payload, headers={
"X-API-Key": KEY
})
print(res.status_code, res.json())
9) Best Practices
- Use enqueue for bulk sends; reserve sync for small, latency‑sensitive messages.
- Retry only on safe statuses (e.g., HTTP
429
afterresetAt
, transient503
). - Normalize recipients (lowercase emails). The API already dedupes, but client‑side hygiene helps.
- Tag and metadata your sends for analytics and later segmentation.
- Templates +
variables
improve consistency; keep your unsubscribe/footer links aligned with the tracking endpoints. - Clicks: wrap in tracking URLs when you build custom HTML. If you use the platform’s template helpers, link rewriting may be handled automatically.
10) Field Reference (JSON)
Field | Type | Required | Notes |
from.email | string | ✓ | Sender email (must be verified for the domain) |
from.name | string | Display name | |
to | string[] | one of | Any of to /cc /bcc must include at least one valid email |
cc | string[] | ||
bcc | string[] | ||
subject | string | ✓ | |
text | string | ||
html | string | ||
reply_to | string | ||
headers | object | Custom headers | |
tags | string[] | Up to your conventions | |
metadata | object | Free‑form JSON | |
template_id | string | Use with templated sends | |
variables | object | Merge variables for templates | |
attachments[].filename | string | ||
attachments[].content | base64 | ||
attachments[].content_type | string | MIME type | |
mode | string | sync to send immediately (or runNow: true ) | |
listHash | string | ✓ (list) | 64‑char hex |
segmentHash | string | ✓ (segment) | 64‑char hex |
11) Security & Compliance
- Key handling: keep API keys server‑side; never expose in client browsers or mobile apps.
- Domain binding: your key is tied to a verified sending domain; use matching
from
addresses. - PII: store only necessary
metadata
. Avoid sensitive data. - Unsubscribe: include the unsubscribe link in bulk/marketing emails.
- DMARC/MTA‑STS/TLS‑RPT: ensure your domain DNS is configured for best deliverability (see platform docs).
12) Glossary
- RID: recipient‑specific tracking token used to link opens/clicks/unsubscribes to a message + email address.
- Queued: accepted for processing; delivery happens asynchronously.
- Sync: send immediately in the request cycle (use sparingly).