Email API
Integrate over standard HTTP and understand request fields, acceptance and message status.
1. Authentication and origin
Send and query with Authorization: Bearer <API Key>. Confirm the API origin and proxy prefix; never add a page locale prefix to API paths. Keep keys on the server. These are standard HTTP examples, not an SMTPDog SDK.
2. Send one message
POST /v1/mail/send accepts JSON. from and to are each a single address string, never an array. Without a template, provide a non-empty subject and at least one of text or html. cc, bcc, attachments, batch and scheduled sending fields are unsupported.
| Field / setting | Type / status | Description |
|---|---|---|
| from | string | One sender, optionally Name <addr>; verified domain; name up to 100 characters |
| to | string | One recipient, not an array |
| subject | string | Required without a template; no CR/LF |
| html / text | string | At least one non-empty body without a template. We recommend sending both html and text; a plain-text alternative can lower the chance of being filtered as spam |
| reply_to | string | Optional single reply-to mailbox |
| headers | map<string,string> | Optional; only List-Unsubscribe, List-Unsubscribe-Post and Auto-Submitted are allowed |
| template_id | uint64 | Optional account-owned template ID |
| variables | map<string,string> | Optional string-valued variables |
curl -X POST "${SMTPDOG_API_ORIGIN}/v1/mail/send" \
-H "Authorization: Bearer ${SMTPDOG_API_KEY}" \
-H "Content-Type: application/json" \
--data '{
"from": "sender@example.com",
"to": "recipient@example.com",
"subject": "Welcome to your product",
"text": "Your message goes here."
}'3. Acceptance and status
A 202 response returns message_id and queued. GET /v1/mail/{message_id} returns the account-owned message status. created_at is present; delivered_at and smtp_response can be omitted. sent can mean the node accepted the message, not inbox placement or reading. Queries with a domain-scoped key are account-owned, without additional domain isolation.
| Field / setting | Type / status | Description |
|---|---|---|
| queued | string | Accepted into queue |
| sending | string | Submission in progress |
| sent | string | Node acceptance or later status; no inbox guarantee |
| deferred | string | Temporary deferral; check later status |
| bounced | string | Bounce |
| failed | string | Failed sending |
{
"message_id": "00000000-0000-4000-8000-000000000000",
"status": "queued"
}4. Use an existing template
template_id must belong to your account. Its subject, html and text fully replace inline values before validation. variables values must be strings. {{name}} uses simple substitution; missing values retain placeholders and HTML values are escaped. Loops and conditional expressions are unsupported. Use the ID of a template you actually created.
{
"from": "sender@example.com",
"to": "recipient@example.com",
"template_id": 123,
"variables": {
"name": "Example recipient"
}
}5. Errors and retries
Normal API errors contain code and error. Branch on code; error is not currently a five-language protocol. Do not repeat permission or configuration failures. A timed-out send may have been accepted. There is no idempotency-key or resend endpoint, so repetition can duplicate sends and quota usage. Failed requests are not guaranteed a quota refund.
| Field / setting | Type / status | Description |
|---|---|---|
| 400 invalid_request | HTTP | Check types, subject and body |
| 401 api_key_invalid | HTTP | Check key and account status |
| 403 domain_unverified / api_key_domain_forbidden / content_blocked | HTTP | Check verification, scope and content |
| 404 template_not_found / message_not_found | HTTP | Check resource ID and account ownership |
| 429 quota_exceeded | HTTP | Check total, daily and hourly limits; avoid blind retries |
| 500 internal_error | HTTP | Keep error context and contact support; processing may be partial |