Sending Emails
Send transactional and marketing emails through the SentinMail API
Overview
SentinMail provides three ways to send emails:
| Method | Endpoint | Use Case |
|---|---|---|
| Send to List | POST /api/emails/send/to-list/ | Send a saved template to an entire subscriber list |
| Send to Receivers | POST /api/emails/send/to-receivers/ | Send a saved template to specific recipients with per-recipient variables |
| Send Custom | POST /api/emails/send/custom/ | Send a custom subject + HTML body (no template needed) |
All sends are processed asynchronously via Celery workers — the API returns immediately with a campaign object and emails are delivered in the background.
Prerequisites
Before sending, you need:
- An API key — see Authentication
- An SMTP configuration — your sending server credentials
- A template (for methods 1 & 2) — the email content with
[[variables]] - A subscriber list (for method 1 only)
When using API key auth, the company field is optional — it's automatically resolved from your key. You only need to pass company when using bearer token auth.
Method 1: Send to Subscriber List
Send a saved template to all subscribers in a list. Supports scheduling.
1curl -X POST https://api.sentinmail.app/api/emails/send/to-list/ \2 -H "X-API-Key: fm_your_api_key_here" \3 -H "Content-Type: application/json" \4 -d '{5 "name": "March Newsletter",6 "template_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",7 "smtp_config_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",8 "list_id": "c3d4e5f6-a7b8-9012-cdef-123456789012"9 }'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
company | UUID | No* | Company ID (*auto-resolved with API key auth) |
name | string | Yes | Campaign name (max 255 chars) |
template_id | UUID | Yes | Email template to send |
smtp_config_id | UUID | Yes | SMTP configuration to use |
list_id | UUID | Yes | Subscriber list to send to |
scheduled_for | ISO-8601 datetime | No | Schedule for later (null = send immediately) |
attachment_ids | UUID[] | No | Media file UUIDs to attach |
is_transactional | boolean | No | Bypass unsubscribe list (default false) — see Transactional Emails |
Schedule for later
1curl -X POST https://api.sentinmail.app/api/emails/send/to-list/ \2 -H "X-API-Key: fm_your_api_key_here" \3 -H "Content-Type: application/json" \4 -d '{5 "name": "Black Friday Campaign",6 "template_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",7 "smtp_config_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",8 "list_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",9 "scheduled_for": "2026-11-29T08:00:00Z"10 }'Response (201 Created):
1{2 "id": "e5f6a7b8-c9d0-1234-efab-345678901234",3 "company": "d4e5f6a7-b8c9-0123-defa-234567890123",4 "name": "March Newsletter",5 "subject": "Your March Update",6 "campaign_type": "to_list",7 "status": "pending",8 "template": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",9 "lists": ["c3d4e5f6-a7b8-9012-cdef-123456789012"],10 "scheduled_for": null,11 "total_emails": 0,12 "sent_count": 0,13 "failed_count": 0,14 "celery_task_id": "task-uuid",15 "created_at": "2026-03-18T10:00:00Z",16 "updated_at": "2026-03-18T10:00:00Z"17}Method 2: Send to Specific Receivers
Send a saved template to a custom list of recipients with per-recipient personalization. No subscriber list required — receivers are specified inline.
1curl -X POST https://api.sentinmail.app/api/emails/send/to-receivers/ \2 -H "X-API-Key: fm_your_api_key_here" \3 -H "Content-Type: application/json" \4 -d '{5 "name": "Renewal Reminder",6 "template_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",7 "smtp_config_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",8 "receivers": [9 {10 "email": "john@example.com",11 "first_name": "John",12 "plan": "Pro",13 "renewal_date": "2026-04-01"14 },15 {16 "email": "jane@example.com",17 "first_name": "Jane",18 "plan": "Enterprise",19 "renewal_date": "2026-04-15"20 }21 ]22 }'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
company | UUID | No* | Company ID (*auto-resolved with API key auth) |
name | string | Yes | Campaign name (max 255 chars) |
template_id | UUID | Yes | Email template to send |
smtp_config_id | UUID | Yes | SMTP configuration to use |
receivers | object[] | Yes | Array of recipient objects (each must have email) |
scheduled_for | ISO-8601 datetime | No | Schedule for later |
attachment_ids | UUID[] | No | Media file UUIDs to attach |
is_transactional | boolean | No | Bypass unsubscribe list (default false) — see Transactional Emails |
Receiver object
Each receiver must have an email field. All other fields are optional and become available as template variables. You can also add cc and bcc arrays to copy additional recipients:
1{2 "email": "john@example.com",3 "first_name": "John",4 "last_name": "Doe",5 "plan": "Enterprise",6 "renewal_date": "2026-04-01",7 "cc": ["manager@example.com"],8 "bcc": ["accounting@internal.com"]9}| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Primary recipient |
cc | string[] | No | Carbon copy recipients (visible to all) |
bcc | string[] | No | Blind carbon copy recipients (hidden from others) |
| any other field | string | No | Available as [[field_name]] template variable |
Response (201 Created):
1{2 "id": "f6a7b8c9-d0e1-2345-fabc-456789012345",3 "company": "d4e5f6a7-b8c9-0123-defa-234567890123",4 "name": "Renewal Reminder",5 "campaign_type": "to_receivers",6 "status": "pending",7 "total_emails": 2,8 "sent_count": 0,9 "failed_count": 0,10 "created_at": "2026-03-18T10:00:00Z"11}Method 3: Send Custom Email
Send a fully custom email with inline subject and HTML body — no saved template needed. Best for transactional emails or one-off messages.
1curl -X POST https://api.sentinmail.app/api/emails/send/custom/ \2 -H "X-API-Key: fm_your_api_key_here" \3 -H "Content-Type: application/json" \4 -d '{5 "name": "Order Confirmation",6 "smtp_config_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",7 "subject": "Order #[[order_id]] confirmed, [[first_name]]!",8 "body": "<h1>Thanks, [[first_name]]!</h1><p>Your order <strong>#[[order_id]]</strong> for [[product]] has been confirmed.</p><p>Total: $[[total]]</p>",9 "receivers": [10 {11 "email": "john@example.com",12 "first_name": "John",13 "order_id": "12345",14 "product": "Wireless Headphones",15 "total": "79.99"16 }17 ]18 }'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
company | UUID | No* | Company ID (*auto-resolved with API key auth) |
name | string | Yes | Campaign name (max 255 chars) |
smtp_config_id | UUID | Yes | SMTP configuration to use |
subject | string | Yes | Email subject line (supports [[variables]]) |
body | string | Yes | Email HTML body (supports [[variables]]) |
receivers | object[] | Yes | Array of recipient objects (each must have email). Supports cc and bcc arrays. |
attachment_ids | UUID[] | No | Media file UUIDs to attach |
is_transactional | boolean | No | Bypass unsubscribe list (default false) — see Transactional Emails |
Custom sends do not support scheduling. Use "Send to List" or "Send to Receivers" if you need scheduled delivery.
Both "Send to Receivers" and "Send Custom" support cc and bcc on each receiver object. See the receiver object docs above for details.
Transactional Emails
Set is_transactional: true on any send request to bypass the unsubscribe list. This means recipients who have previously unsubscribed will still receive the email.
1curl -X POST https://api.sentinmail.app/api/emails/send/custom/ \2 -H "X-API-Key: fm_your_api_key_here" \3 -H "Content-Type: application/json" \4 -d '{5 "name": "Password Reset",6 "smtp_config_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",7 "subject": "Reset your password",8 "body": "<p>Click <a href=\"[[reset_link]]\">here</a> to reset your password.</p>",9 "is_transactional": true,10 "receivers": [{ "email": "user@example.com", "reset_link": "https://..." }]11 }'Only use is_transactional for emails the recipient must receive regardless of marketing preferences:
- Password resets
- Email verification
- Purchase receipts and invoices
- Account security alerts
- Subscription billing notifications
Using it for newsletters or promotional emails violates anti-spam laws (CAN-SPAM, GDPR) and your recipients' trust.
All three send methods support is_transactional. It defaults to false — normal marketing sends always respect the unsubscribe list.
Template Variables
All three methods support personalization using [[variable]] syntax. Variables are replaced per-recipient.
Built-in variables
| Variable | Description |
|---|---|
[[email]] | Recipient's email address |
[[first_name]] | Recipient's first name |
[[last_name]] | Recipient's last name |
[[unsubscribe_url]] | Auto-generated one-click unsubscribe link |
[[view_in_browser_url]] | Link to view the email in a browser |
Custom variables
Any field you include in a receiver object is available as a variable:
1{2 "email": "john@example.com",3 "first_name": "John",4 "company_name": "Acme Corp",5 "coupon_code": "SAVE20"6}Use in template: Hi [[first_name]] from [[company_name]]! Use code [[coupon_code]] for 20% off.
Conditionals
1[[#if coupon_code]]2 Use code <strong>[[coupon_code]]</strong> for a discount!3[[#else]]4 Check out our latest deals.5[[/if]]Loops
1[[#each items]]2 <li>[[this]]</li>3[[/each]]Campaign Management
Check campaign status
1curl -X GET https://api.sentinmail.app/api/emails/campaigns/CAMPAIGN_ID/ \2 -H "X-API-Key: fm_your_api_key_here"Resume a paused/failed campaign
If a campaign partially completes (e.g., SMTP rate limit hit), resume it:
1curl -X POST https://api.sentinmail.app/api/emails/campaigns/CAMPAIGN_ID/resume/ \2 -H "X-API-Key: fm_your_api_key_here"Only campaigns with status partial or failed (with some sent emails) can be resumed. Already-sent recipients are skipped.
Cancel a scheduled campaign
1curl -X POST https://api.sentinmail.app/api/emails/campaigns/CAMPAIGN_ID/cancel/ \2 -H "X-API-Key: fm_your_api_key_here"Only campaigns with status scheduled can be cancelled.
Email Headers
SentinMail automatically adds these headers to every outgoing email:
1From: <sender_name> <sender_email>2Reply-To: <reply_to_email>3Message-ID: <auto-generated>4X-Mailer: SentinMail5Precedence: bulk6List-Unsubscribe: <unsubscribe_url>7List-Unsubscribe-Post: List-Unsubscribe=One-Click8List-Id: <list_name> <sender_domain>Error Responses
Validation error (400)
1{2 "template_id": ["Template not found."],3 "receivers": ["Receiver at index 0 missing \"email\" key."]4}Permission denied (403)
1{2 "detail": {3 "code": "feature_not_available",4 "message": "Your Free plan does not include scheduled_campaigns.",5 "feature": "scheduled_campaigns",6 "package": "Free"7 }8}Rate limit exceeded (429)
1{2 "detail": {3 "code": "rate_limit_exceeded",4 "message": "Your Free plan allows 100 API calls per day. Limit reached.",5 "limit": 100,6 "current": 1007 }8}SMTP rate limit (429)
1{2 "detail": {3 "code": "smtp_rate_limited",4 "message": "Hourly limit (50) would be exceeded.",5 "limit_type": "hourly"6 }7}Quick Reference
| What you want to do | Method | Endpoint |
|---|---|---|
| Newsletter to all subscribers | Send to List | POST /api/emails/send/to-list/ |
| Personalized email to specific people | Send to Receivers | POST /api/emails/send/to-receivers/ |
| One-off email with custom HTML | Send Custom | POST /api/emails/send/custom/ |
| Bypass unsubscribe list (password reset, receipt, etc.) | Any method | Add "is_transactional": true |
| Schedule a campaign for later | Send to List / Receivers | Add scheduled_for field |
| Resume a failed send | Resume | POST /api/emails/campaigns/{id}/resume/ |
| Cancel a scheduled send | Cancel | POST /api/emails/campaigns/{id}/cancel/ |
| Validate SMTP credentials | Validate | POST /api/emails/smtp/validate/ |