Send transactional emails, manage templates, track delivery, and handle inbound messages.
Sending Email
Send transactional emails using a template or raw HTML. Every email is tracked for delivery, opens, and clicks automatically.
// Send an email using a template
curl -X POST https://vm.whaletools.cloud/v1/stores/{store_id}/email/send \
-H "x-api-key: wk_live_..." \
-H "Content-Type: application/json" \
-d '{
"to": "jane@example.com",
"template_id": "tmpl_order_confirmation",
"variables": {
"customer": { "name": "Jane Smith" },
"order": {
"number": "#10042",
"total": "$47.50",
"items": [
{ "name": "Running Shoes", "quantity": 1, "price": "$39.99" },
{ "name": "Sport Socks", "quantity": 2, "price": "$3.75" }
]
}
},
"tags": ["order-confirmation", "transactional"]
}'
// Response
{
"object": "email_message",
"id": "msg_550e8400-e29b-41d4-a716-446655440000",
"to": "jane@example.com",
"subject": "Order #10042 Confirmed",
"status": "queued",
"template_id": "tmpl_order_confirmation",
"created_at": "2026-03-10T12:00:00.000Z"
}
// Send a raw HTML email
curl -X POST https://vm.whaletools.cloud/v1/stores/{store_id}/email/send \
-H "x-api-key: wk_live_..." \
-H "Content-Type: application/json" \
-d '{
"to": "jane@example.com",
"from": "hello@your-store.com",
"subject": "Your receipt from My Store",
"html": "<h1>Thanks for your purchase!</h1><p>Order #10042 has been confirmed.</p>",
"text": "Thanks for your purchase! Order #10042 has been confirmed.",
"reply_to": "support@your-store.com"
}'Email Templates
Templates use Handlebars syntax for dynamic content. Create reusable templates for order confirmations, shipping updates, password resets, and marketing campaigns.
| Variable | Description |
|---|---|
| {{customer.name}} | Customer full name. |
| {{customer.email}} | Customer email address. |
| {{order.number}} | Order number (e.g., #10042). |
| {{order.total}} | Formatted order total with currency. |
| {{order.items}} | Array of line items (iterable). |
| {{store.name}} | Your store name. |
| {{store.url}} | Your storefront URL. |
| {{unsubscribe_url}} | One-click unsubscribe link (required). |
// Create a template
curl -X POST https://vm.whaletools.cloud/v1/stores/{store_id}/email/templates \
-H "x-api-key: wk_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Order Confirmation",
"slug": "order_confirmation",
"subject": "Order {{order.number}} Confirmed",
"html": "<!DOCTYPE html><html><body style=\"font-family: sans-serif;\"><h1>Thanks, {{customer.name}}!</h1><p>Your order {{order.number}} for {{order.total}} has been confirmed.</p>{{#each order.items}}<p>{{this.name}} x{{this.quantity}} — {{this.price}}</p>{{/each}}<hr><p style=\"font-size: 12px; color: #888;\"><a href=\"{{unsubscribe_url}}\">Unsubscribe</a></p></body></html>",
"text": "Thanks, {{customer.name}}! Your order {{order.number}} for {{order.total}} has been confirmed."
}'
// Response
{
"object": "email_template",
"id": "tmpl_order_confirmation",
"name": "Order Confirmation",
"slug": "order_confirmation",
"version": 1,
"created_at": "2026-03-10T12:00:00.000Z"
}
// List templates
curl https://vm.whaletools.cloud/v1/stores/{store_id}/email/templates \
-H "x-api-key: wk_live_..."
// Preview a template with sample data
curl -X POST https://vm.whaletools.cloud/v1/stores/{store_id}/email/templates/{template_id}/preview \
-H "x-api-key: wk_live_..." \
-H "Content-Type: application/json" \
-d '{
"variables": {
"customer": { "name": "Jane Smith" },
"order": { "number": "#10042", "total": "$47.50", "items": [] }
}
}'Domain Setup and Verification
Send emails from your own domain for better deliverability and brand consistency. Add the required DNS records and verify via the API.
// Add a sending domain
curl -X POST https://vm.whaletools.cloud/v1/stores/{store_id}/email/domains \
-H "x-api-key: wk_live_..." \
-H "Content-Type: application/json" \
-d '{ "domain": "your-store.com" }'
// Response — includes the DNS records to add
{
"object": "email_domain",
"id": "dom_550e8400",
"domain": "your-store.com",
"status": "pending",
"dns_records": [...]
}Add the following DNS records to your domain:
| Type | Name | Value | Purpose |
|---|---|---|---|
| TXT | @ | v=spf1 include:_spf.whaletools.dev ~all | SPF — authorizes WhaleTools to send on your behalf. |
| CNAME | wt._domainkey | wt.dkim.whaletools.dev | DKIM — cryptographic email signing. |
| CNAME | _dmarc | dmarc.whaletools.dev | DMARC — email authentication policy. |
| CNAME | tracking | track.whaletools.dev | Click and open tracking via your domain. |
// Verify domain after adding DNS records
curl -X POST https://vm.whaletools.cloud/v1/stores/{store_id}/email/domains/{domain_id}/verify \
-H "x-api-key: wk_live_..."
// Response
{
"object": "email_domain",
"id": "dom_550e8400",
"domain": "your-store.com",
"status": "verified",
"spf": "valid",
"dkim": "valid",
"dmarc": "valid",
"verified_at": "2026-03-10T12:05:00.000Z"
}Email Tracking
Every email is automatically tracked for delivery, opens, clicks, bounces, and complaints. Query events per message or in aggregate.
| Event | Description |
|---|---|
| delivered | Email was accepted by the recipient mail server. |
| opened | Recipient opened the email (pixel tracking). |
| clicked | Recipient clicked a link in the email. |
| bounced | Email could not be delivered (hard or soft bounce). |
| complained | Recipient marked the email as spam. |
| unsubscribed | Recipient clicked the unsubscribe link. |
// Get events for a specific message
curl https://vm.whaletools.cloud/v1/stores/{store_id}/email/messages/{message_id}/events \
-H "x-api-key: wk_live_..."
// Response
{
"object": "list",
"data": [
{
"event": "delivered",
"timestamp": "2026-03-10T12:00:02.000Z",
"details": { "smtp_response": "250 OK" }
},
{
"event": "opened",
"timestamp": "2026-03-10T12:05:30.000Z",
"details": { "user_agent": "Apple Mail", "ip": "203.0.113.42" }
},
{
"event": "clicked",
"timestamp": "2026-03-10T12:06:15.000Z",
"details": { "url": "https://your-store.com/orders/10042" }
}
]
}
// Aggregate stats for a date range
curl https://vm.whaletools.cloud/v1/stores/{store_id}/email/stats \
-H "x-api-key: wk_live_..." \
-G -d "date_from=2026-03-01" -d "date_to=2026-03-10"
// Response
{
"date_range": { "from": "2026-03-01", "to": "2026-03-10" },
"sent": 4250,
"delivered": 4180,
"opened": 1840,
"clicked": 620,
"bounced": 45,
"complained": 3,
"unsubscribed": 12,
"open_rate": 44.0,
"click_rate": 14.8,
"bounce_rate": 1.1
}Inbox and Threads
WhaleTools provides a shared inbox for receiving and replying to customer emails. Messages are organized into threads and linked to customer profiles automatically.
// List inbox threads
curl https://vm.whaletools.cloud/v1/stores/{store_id}/email/threads \
-H "x-api-key: wk_live_..." \
-G -d "status=open" -d "limit=20"
// Response
{
"object": "list",
"data": [
{
"id": "thread_a1b2c3d4",
"subject": "Re: Order #10042 — Where is my package?",
"customer_id": "cust_550e8400",
"customer_email": "jane@example.com",
"status": "open",
"message_count": 3,
"last_message_at": "2026-03-10T14:30:00.000Z",
"assigned_to": null
}
],
"has_more": true
}
// Reply to a thread
curl -X POST https://vm.whaletools.cloud/v1/stores/{store_id}/email/threads/{thread_id}/reply \
-H "x-api-key: wk_live_..." \
-H "Content-Type: application/json" \
-d '{
"html": "<p>Hi Jane, your order shipped yesterday via USPS. Tracking: 9400111899223456789012. It should arrive by Wednesday.</p>",
"text": "Hi Jane, your order shipped yesterday via USPS. Tracking: 9400111899223456789012. It should arrive by Wednesday."
}'Best Practices
- •Verify your sending domain. Authenticated domains (SPF + DKIM + DMARC) have significantly better inbox placement rates.
- •Always include a text version. Some email clients and spam filters prefer plain text. The
textfield is required for all templates. - •Include an unsubscribe link. Use the
{{unsubscribe_url}}variable in every marketing email. This is required by law (CAN-SPAM, GDPR). - •Monitor bounce rates. Hard bounces above 2% can damage your sender reputation. Remove invalid addresses promptly.
- •Use tags for filtering. Add tags like
transactionalormarketingto categorize emails and filter analytics. - •Preview before sending. Use the template preview endpoint to render with sample data and verify formatting before going live.