HTTP API
Applications that aren't Node can integrate directly over HTTP. The send API is one endpoint, authenticated with a project API key.
Authentication
Pass the key as a bearer token. Keys are project-scoped; the template you reference must belong to the same project.
Authorization: Bearer solen_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxPOST /v1/emails
curl -X POST http://localhost:4000/v1/emails \
-H "Authorization: Bearer $SOLEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "welcome-email",
"to": "student@example.com",
"variables": { "name": "Priya", "school": "Northside High" }
}'| Field | Type | Required | Notes |
|---|---|---|---|
template | string | yes | Template slug within the key's project. |
to | string | yes | Recipient email address. |
variables | object | no | String values substituted into {{placeholders}}. |
attachments | array | no | Files to attach (base64). Appended after the template's own attachments. |
Attachments
Each attachment is an object with a filename, base64 content, and an optional contentType. They are appended to any attachments configured on the template in the dashboard.
curl -X POST http://localhost:4000/v1/emails \
-H "Authorization: Bearer $SOLEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "invoice",
"to": "user@example.com",
"variables": { "amount": "$42.10" },
"attachments": [
{
"filename": "invoice.pdf",
"content": "JVBERi0xLjcKJ...",
"contentType": "application/pdf"
}
]
}'| Field | Type | Required | Notes |
|---|---|---|---|
filename | string | yes | File name shown to the recipient. |
content | string | yes | File bytes, base64-encoded. |
contentType | string | no | MIME type; derived from the filename when omitted. |
Limits: up to 20 attachments and 10 MB total decoded size per email (template + request combined). Over the limit returns INVALID_PAYLOAD.
Success - 200
{
"id": "log_cmb1xk2...",
"status": "SUCCESS",
"messageId": "re_AbC123..."
}Provider failure - 422
{
"id": "log_cmb1xk9...",
"status": "FAILED",
"error": { "code": "PROVIDER_ERROR", "message": "Domain not verified" }
}Both outcomes write a log row; id is always the log id. Other errors use the standard error shape without an id when nothing was renderable to log (e.g. unknown template).
Error responses
| Status | Code | When |
|---|---|---|
401 | UNAUTHORIZED | Missing, malformed, revoked, or unknown key. |
404 | TEMPLATE_NOT_FOUND | Slug doesn't exist in the key's project. |
409 | TEMPLATE_DISABLED | Template is toggled off. |
400 | INVALID_PAYLOAD | Validation failure; message says which field. |
422 | PROVIDER_ERROR | Provider rejected the send (logged). |
429 | RATE_LIMITED | Over 60 requests/minute on one key. |
GET /v1/health
Unauthenticated liveness probe; returns { "ok": true }.
Rate limits
The send API allows 60 requests per minute per key. Batch and queue sends client-side for now - bulk sending is a Phase 3 roadmap item.