Base URL: https://api.opensend.dev (hosted) or http://localhost:3000 (self-hosted)
All API requests require authentication via Bearer token:
Authorization: Bearer mf_your_api_key
API keys are prefixed with mf_ for easy identification.
All responses use token-efficient JSON. We minimize payload size to preserve context windows for AI agents.
{
"id": "msg_abc123",
"status": "queued"
}{
"error": "invalid_recipient",
"message": "Email address format is invalid"
}| Code | Description |
|---|---|
invalid_request |
Malformed request body |
invalid_recipient |
Invalid email address format |
invalid_sender |
Sender domain not verified |
rate_limited |
Rate limit exceeded |
suppressed |
Recipient is on suppression list |
unauthorized |
Invalid or missing API key |
not_found |
Resource not found |
server_error |
Internal server error |
Default limits (configurable for self-hosted):
| Window | Limit |
|---|---|
| Per minute | 100 requests |
| Per hour | 1,000 requests |
| Per day | 10,000 requests |
Rate limit headers are included in all responses:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1706450400
When rate limited, the API returns 429 Too Many Requests:
{
"error": "rate_limited",
"message": "Rate limit exceeded",
"retry_after": 45
}POST /v1/email/send
Send a single email message.
Request:
{
"to": "recipient@example.com",
"from": "sender@yourdomain.com",
"subject": "Hello",
"text": "Plain text body",
"html": "<p>HTML body (optional)</p>",
"reply_to": "reply@yourdomain.com",
"headers": {
"X-Custom-Header": "value"
}
}| Field | Type | Required | Description |
|---|---|---|---|
to |
string | Yes | Recipient email address |
from |
string | Yes | Sender email (domain must be verified) |
subject |
string | Yes | Email subject line |
text |
string | Yes* | Plain text body |
html |
string | No | HTML body (overrides text for HTML clients) |
reply_to |
string | No | Reply-to address |
headers |
object | No | Custom headers (X- prefixed) |
*Either text or html is required.
Response:
{
"id": "msg_abc123",
"status": "queued"
}Status Values:
queued- Message accepted, pending deliverysent- Message delivered to recipient serverdelivered- Delivery confirmed (if webhook configured)bounced- Delivery failed permanentlyfailed- Sending error
GET /v1/email/{id}
Retrieve the status of a sent email.
Request:
curl https://api.opensend.dev/v1/email/msg_abc123 \
-H "Authorization: Bearer mf_your_api_key"Response:
{
"id": "msg_abc123",
"status": "delivered",
"to": "recipient@example.com",
"from": "sender@yourdomain.com",
"subject": "Hello",
"created_at": "2026-01-28T10:30:00Z",
"delivered_at": "2026-01-28T10:30:05Z"
}POST /v1/domains/verify
Initiate domain verification. Returns DNS records to configure.
Request:
{
"domain": "yourdomain.com"
}Response:
{
"domain": "yourdomain.com",
"status": "pending",
"records": [
{
"type": "TXT",
"name": "_opensend",
"value": "opensend-verify=abc123"
},
{
"type": "TXT",
"name": "opensend._domainkey",
"value": "v=DKIM1; k=rsa; p=MIGfMA0..."
},
{
"type": "TXT",
"name": "_dmarc",
"value": "v=DMARC1; p=quarantine; rua=mailto:dmarc@opensend.dev"
}
]
}GET /v1/domains/{domain}
Check verification status for a domain.
Request:
curl https://api.opensend.dev/v1/domains/yourdomain.com \
-H "Authorization: Bearer mf_your_api_key"Response:
{
"domain": "yourdomain.com",
"status": "verified",
"verified_at": "2026-01-28T10:00:00Z",
"spf": true,
"dkim": true,
"dmarc": true
}Status Values:
pending- Awaiting DNS configurationverified- Domain verified and ready to sendfailed- Verification failed (check DNS)
GET /v1/suppressions
Get list of suppressed email addresses (bounces, complaints, unsubscribes).
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
type |
string | Filter by type: bounce, complaint, unsubscribe |
limit |
integer | Results per page (default: 100, max: 1000) |
cursor |
string | Pagination cursor |
Request:
curl "https://api.opensend.dev/v1/suppressions?type=bounce&limit=50" \
-H "Authorization: Bearer mf_your_api_key"Response:
{
"data": [
{
"email": "bounced@example.com",
"type": "bounce",
"reason": "mailbox_full",
"created_at": "2026-01-28T10:00:00Z"
}
],
"cursor": "eyJpZCI6MTAwfQ"
}DELETE /v1/suppressions/{email}
Remove an email from the suppression list.
Request:
curl -X DELETE https://api.opensend.dev/v1/suppressions/user@example.com \
-H "Authorization: Bearer mf_your_api_key"Response:
{
"deleted": true
}POST /v1/webhooks
Register a webhook endpoint for delivery events.
Request:
{
"url": "https://yourapp.com/webhooks/opensend",
"events": ["delivered", "bounced", "complained"]
}| Field | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | HTTPS webhook endpoint |
events |
array | Yes | Events to subscribe to |
Event Types:
queued- Message queued for deliverysent- Message sent to recipient serverdelivered- Delivery confirmedbounced- Hard bounce (permanent failure)soft_bounced- Soft bounce (temporary failure)complained- Spam complaint receivedunsubscribed- Recipient unsubscribed
Response:
{
"id": "wh_xyz789",
"url": "https://yourapp.com/webhooks/opensend",
"events": ["delivered", "bounced", "complained"],
"secret": "whsec_abc123"
}Webhook Payload:
{
"event": "delivered",
"message_id": "msg_abc123",
"timestamp": "2026-01-28T10:30:05Z",
"recipient": "user@example.com"
}Signature Verification:
Webhooks include a signature header for verification:
X-OpenSend-Signature: sha256=abc123...
Verify by computing HMAC-SHA256 of the raw body with your webhook secret.
GET /health
Check API server health. No authentication required.
Response:
{
"status": "ok",
"version": "0.1.0"
}const response = await fetch('https://api.opensend.dev/v1/email/send', {
method: 'POST',
headers: {
'Authorization': 'Bearer mf_your_api_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: 'user@example.com',
from: 'hello@yourdomain.com',
subject: 'Hello',
text: 'Message body'
})
});
const { id, status } = await response.json();
console.log(`Message ${id}: ${status}`);import requests
response = requests.post(
'https://api.opensend.dev/v1/email/send',
headers={'Authorization': 'Bearer mf_your_api_key'},
json={
'to': 'user@example.com',
'from': 'hello@yourdomain.com',
'subject': 'Hello',
'text': 'Message body'
}
)
data = response.json()
print(f"Message {data['id']}: {data['status']}")curl -X POST https://api.opensend.dev/v1/email/send \
-H "Authorization: Bearer mf_your_api_key" \
-H "Content-Type: application/json" \
-d '{"to":"user@example.com","from":"hello@yourdomain.com","subject":"Hello","text":"Message body"}'| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request - Invalid parameters |
| 401 | Unauthorized - Invalid API key |
| 404 | Not Found |
| 429 | Rate Limited |
| 500 | Server Error |
Official SDKs (coming soon):
- Node.js:
@opensend/sdk - Python:
opensend-python - Go:
opensend-go
For now, use the REST API directly or the MCP server for AI agents.