- developer
Cold Email API: The Developer Guide to Programmatic Outbound
Want to integrate cold email into your product or workflow? Here's the developer guide to programmatic outbound: endpoints, auth, code examples, and best practices.
SendEmAll Team
The SendEmAll Team
Why developers want a cold email API
You have a CRM with 5,000 contacts. A webhook fires when a prospect hits a lead score threshold. You want that prospect to automatically enter a cold email sequence — no manual CSV upload, no clicking through a UI.
Or you’re building a product that includes outbound as a feature. Your customers define their ICP in your app, and you orchestrate discovery, personalization, and sending behind the scenes.
Or you’re a RevOps engineer tired of duct-taping Zapier workflows between 6 different tools.
In all three cases, you need an API. Not a UI with an API bolted on as an afterthought. A real API built for programmatic access.
What to look for in a cold email API
Not all APIs are created equal. Here’s what separates a real API from a “we technically have endpoints” API.
Must-haves:
| Capability | Why it matters |
|---|---|
| Campaign CRUD | Create, update, pause, delete campaigns programmatically |
| Lead management | Add, update, remove leads from campaigns via API |
| Sending triggers | Start, pause, resume sending via API calls |
| Event webhooks | Get notified of opens, replies, bounces in real-time |
| Rate limit documentation | Know your limits before you hit them |
| Authentication | API key or OAuth, well-documented |
| Error responses | Structured error codes, not just 500s |
Nice-to-haves:
- SDKs in popular languages (Python, JavaScript, Go)
- Sandbox/test mode
- Idempotent endpoints (safe to retry)
- Batch operations (add 100 leads in one call, not 100 calls)
- Webhook signature verification
SendEmAll API overview
The SendEmAll API is a REST API. JSON in, JSON out. Authentication via API key passed in the x-user-context header.
Base URL: https://api.sendemall.com/v1
Authentication: Every request requires an API key in the header:
x-user-context: your-api-key-here
Response format:
{
"success": true,
"data": { ... },
"meta": {
"request_id": "req_abc123",
"timestamp": "2026-04-06T12:00:00Z"
}
}
Errors return a structured response:
{
"success": false,
"error": {
"code": "CAMPAIGN_NOT_FOUND",
"message": "Campaign with ID cmp_xyz does not exist",
"status": 404
}
}
Code examples
Creating a campaign
curl:
curl -X POST https://api.sendemall.com/v1/campaigns \
-H "Content-Type: application/json" \
-H "x-user-context: your-api-key" \
-d '{
"name": "Q2 Series B Outreach",
"daily_limit": 50,
"timezone": "America/New_York",
"schedule": {
"days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
"start_hour": 8,
"end_hour": 17
},
"sequences": [
{
"step": 1,
"delay_days": 0,
"subject": "{{company}} and pipeline visibility",
"body": "Hi {{first_name}},\n\n{{ai_personalized_opening}}\n\n{{value_prop}}\n\nWorth a quick look?"
},
{
"step": 2,
"delay_days": 3,
"subject": "Re: {{company}} and pipeline visibility",
"body": "Hi {{first_name}},\n\nQuick follow-up on my last note. {{case_study_snippet}}\n\nHappy to share more details if relevant."
}
]
}'
Python:
import requests
api_key = "your-api-key"
base_url = "https://api.sendemall.com/v1"
campaign = requests.post(
f"{base_url}/campaigns",
headers={
"Content-Type": "application/json",
"x-user-context": api_key,
},
json={
"name": "Q2 Series B Outreach",
"daily_limit": 50,
"timezone": "America/New_York",
"schedule": {
"days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
"start_hour": 8,
"end_hour": 17,
},
"sequences": [
{
"step": 1,
"delay_days": 0,
"subject": "{{company}} and pipeline visibility",
"body": "Hi {{first_name}},\n\n{{ai_personalized_opening}}\n\n{{value_prop}}\n\nWorth a quick look?",
}
],
},
)
campaign_id = campaign.json()["data"]["id"]
print(f"Created campaign: {campaign_id}")
JavaScript (Node.js):
const response = await fetch("https://api.sendemall.com/v1/campaigns", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-user-context": "your-api-key",
},
body: JSON.stringify({
name: "Q2 Series B Outreach",
daily_limit: 50,
timezone: "America/New_York",
schedule: {
days: ["monday", "tuesday", "wednesday", "thursday", "friday"],
start_hour: 8,
end_hour: 17,
},
sequences: [
{
step: 1,
delay_days: 0,
subject: "{{company}} and pipeline visibility",
body: "Hi {{first_name}},\n\n{{ai_personalized_opening}}\n\n{{value_prop}}\n\nWorth a quick look?",
},
],
}),
});
const { data } = await response.json();
console.log(`Created campaign: ${data.id}`);
Adding leads to a campaign
curl -X POST https://api.sendemall.com/v1/campaigns/cmp_abc123/leads \
-H "Content-Type: application/json" \
-H "x-user-context: your-api-key" \
-d '{
"leads": [
{
"email": "sarah@example.com",
"first_name": "Sarah",
"last_name": "Chen",
"company": "Acme Corp",
"title": "VP of Engineering",
"custom_fields": {
"tech_stack": "Kubernetes, Go, Postgres",
"recent_hire_count": 5
}
}
]
}'
Batch limit: 100 leads per request. For larger imports, make multiple requests with a 1-second delay between them.
Triggering a send
curl -X POST https://api.sendemall.com/v1/campaigns/cmp_abc123/activate \
-H "x-user-context: your-api-key"
The campaign starts sending according to its schedule. To pause:
curl -X POST https://api.sendemall.com/v1/campaigns/cmp_abc123/pause \
-H "x-user-context: your-api-key"
Listening for webhook events
Register a webhook endpoint:
curl -X POST https://api.sendemall.com/v1/webhooks \
-H "Content-Type: application/json" \
-H "x-user-context: your-api-key" \
-d '{
"url": "https://your-app.com/webhooks/sendemall",
"events": ["email.opened", "email.replied", "email.bounced"],
"secret": "your-webhook-secret"
}'
When an event occurs, SendEmAll sends a POST to your URL:
{
"event": "email.replied",
"timestamp": "2026-04-06T14:23:00Z",
"data": {
"campaign_id": "cmp_abc123",
"lead_id": "lead_xyz789",
"email": "sarah@example.com",
"sequence_step": 1,
"reply_sentiment": "positive",
"reply_preview": "Thanks for reaching out. We're actually evaluating..."
},
"signature": "sha256=abc123..."
}
Verify the signature before processing:
import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
Rate limits and best practices
Rate limits:
- 60 requests per minute per API key (general endpoints)
- 10 requests per minute for campaign creation
- 100 leads per batch add request
- Webhook delivery: up to 3 retries with exponential backoff
Best practices:
-
Use idempotency keys for create operations. Pass an
Idempotency-Keyheader to prevent duplicate campaigns or leads from retried requests. -
Handle rate limits gracefully. 429 responses include a
Retry-Afterheader. Respect it. -
Verify webhook signatures. Always. Unsigned webhook payloads should be rejected.
-
Don’t poll for status. Use webhooks instead of polling campaign or lead status endpoints. It’s faster and uses zero rate limit budget.
-
Store the
request_idfrom every API response. If something goes wrong, this ID helps support trace the issue. -
Test in sandbox first. Use
https://sandbox.api.sendemall.com/v1to test your integration without sending real emails or consuming credits.
Common integration patterns
CRM-triggered outbound: Lead scores above threshold in your CRM → webhook to your server → API call to add lead to SendEmAll campaign → reply webhook from SendEmAll → update CRM with reply status.
Product-led outbound: User signs up for free tier → after 14 days without converting → API call adds them to a re-engagement campaign → reply webhook pauses the campaign for that user if they respond.
Multi-tool orchestration: Your data warehouse identifies ICP-matching companies → your pipeline pushes them to SendEmAll via API → replies trigger a Slack notification → meetings get logged in your CRM.
For the full API reference, authentication details, and SDKs, visit the developer documentation.
Start building — your API key is available immediately after signup.
Stop emailing strangers. Start closing buyers.
From 200+ outbound teams