Email Sending API
Email endpoints accept both cookie auth and API key auth (Authorization: Bearer sk_...).
Send
POST /api/v1/projects/{id}/sendOne endpoint for all individual sends. The behavior depends on the fields you provide.
Send template to any email
{
"to": "user@example.com",
"template_id": "uuid",
"subject": "Optional override",
"data": {
"name": "John Doe",
"email": "john@example.com"
}
}Sends a template to any email address. No subscriber needed. Variables like in the template are replaced with values from data. If subject is provided, it overrides the template's subject.
Send template to a subscriber
{
"subscriber_id": "uuid",
"template_id": "uuid"
}Sends a template to a specific subscriber. Variables , , and are replaced automatically with the subscriber's data.
Send raw HTML (no template)
{
"to": "user@example.com",
"subject": "Password Reset",
"html_body": "<p>Click <a href='...'>here</a> to reset.</p>"
}Sends a one-off email without a template. All three fields are required.
Response
{"message": "sent"}Or for subscriber sends:
{"sent": 1, "failed": 0}Batch Send
POST /api/v1/projects/{id}/send/batch{
"template_id": "uuid",
"subject": "Optional override",
"recipients": [
{"to": "user1@example.com", "data": {"name": "John"}},
{"to": "user2@example.com", "data": {"name": "Jane"}},
{"to": "user3@example.com", "data": {"name": "Bob"}}
]
}Sends a template to multiple recipients in one request. Each recipient can have its own data for variable replacement. Ideal for campaigns, notifications, or migrating from external systems that provide a list of emails.
Response
{"sent": 3, "failed": 0}Broadcast
POST /api/v1/projects/{id}/broadcastSends a template to all active subscribers in the project. Separated from /send for safety.
{"template_id": "uuid"}Variables are replaced per subscriber. The is injected automatically with a link to the public unsubscribe page.
Response
{"sent": 150, "failed": 2}Unsubscribe
GET /unsubscribe/{projectId}/{subscriberId}Public endpoint (no auth required). Shows a confirmation page and sets the subscriber's status to unsubscribed. The URL is auto-generated and injected via in broadcast and subscriber sends.
Test SMTP
POST /api/v1/projects/{id}/smtp/testSends a test email to verify SMTP configuration. Cookie auth only.
Open Tracking
GET /t/{logId}.gifPublic endpoint (no auth required). Returns a 1x1 transparent GIF pixel. This pixel is automatically injected into emails sent to subscribers and via broadcast. When the recipient's email client loads the image, SendDock records the opened_at timestamp on the corresponding email log entry. Only the first open is recorded.
Click Tracking
GET /c/{logId}/{payload}Public endpoint (no auth required). {payload} has the form <base64url-encoded-URL>.<hmac-token> and is generated automatically when SendDock rewrites <a href> tags in outgoing emails.
The handler verifies the HMAC against JWT_SECRET, records the click (first click sets clicked_at on the email log; every click appends a row to email_clicks with URL, user agent and IP), then returns 302 Found to the original URL.
Tampered or invalid tokens return 400 Bad Request. Click tracking is on by default for every outgoing email; it does not need to be enabled per project.
Email Logs
GET /api/v1/projects/{id}/logs?limit=50&offset=0Cookie auth only.
Filters
| Parameter | Description | Example |
|---|---|---|
status | Filter by delivery status | ?status=sent or ?status=failed |
from | Start date (RFC 3339) | ?from=2026-01-01T00:00:00Z |
to | End date (RFC 3339) | ?to=2026-02-01T00:00:00Z |
Example with filters:
GET /api/v1/projects/{id}/logs?status=sent&from=2026-01-01T00:00:00Z&to=2026-02-01T00:00:00Z&limit=50&offset=0{
"logs": [
{
"id": "uuid",
"project_id": "uuid",
"subscriber_id": "uuid",
"template_id": "uuid",
"to_email": "user@example.com",
"subject": "Welcome!",
"status": "sent",
"error": null,
"sent_at": "2026-01-01T00:00:00Z",
"opened_at": "2026-01-01T01:23:45Z"
}
],
"total": 1520
}Stats
GET /api/v1/projects/{id}/stats{"total": 1520, "sent": 1500, "failed": 20, "opened": 980}