API reference / Outreach
Send one email per request from a mailbox on your verified sending domain. You can write the email yourself or merge a saved template with a contact. To email a whole list, use an email campaign.
A
202means accepted, not delivered. Delivery, opens, clicks, bounces and complaints arrive on thecontact.email.statuswebhook. Calling hours, the contact frequency limit and retries are explained in Send lifecycle. What eachreason_codemeans, and what to do about it, is in Outcomes and what to do.
Routes
| Method | Route | Scope | What it does |
|---|---|---|---|
POST |
/email/public/email |
email:send |
Send an email you wrote |
POST |
/email/public/email/merged-template |
email:send |
Merge a saved template with a contact and send it |
GET |
/email/public/email |
contacts:read |
List sent and received emails |
GET |
/domain/public/mailboxes |
email:read |
List the mailboxes you can send from |
Before you send
- Set up a sending domain. Verify a domain and create a mailbox on it in
the dashboard. Without one, sends answer
success: false. - Pick the send type.
transactional, the default, is for receipts, reminders and replies.bulkis for marketing: it adds unsubscribe handling and uses the bulk frequency limit. A sending domain set to one purpose overrides what you pass. - Follow CAN-SPAM for marketing. Use an accurate sender and subject, include your postal address and a working unsubscribe, and don't email people who opted out.
Send an email
POST /email/public/email sends the subject and HTML you supply.
| Field | Type | Required | Description |
|---|---|---|---|
to |
object[] | One of to or contact_id |
Recipients as { "address", "name" }. An address with no contact creates one. |
contact_id |
string | One of to or contact_id |
Send to the contact's primary email. |
mailbox_id |
string | One of mailbox_id or from |
The mailbox to send from. Use this when you can. |
from |
object[] | One of mailbox_id or from |
One { "address", "name" } on a verified domain |
subject |
string | Yes | Subject line |
html |
string | Yes | HTML body |
text |
string | No | Plain-text version |
preview |
string | No | Preview text that most inboxes show after the subject |
cc, bcc |
object[] | No | More recipients as { "address", "name" } |
send_type |
string | No | transactional (default) or bulk |
template_id |
string | No | Records which template this email came from. Nothing is merged. |
curl -X POST https://api-v2.dropcowboy.com/email/public/email \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
-H "Idempotency-Key: 2f9b4d7e-1a3c-4e6b-8d2f-5c7a9e1b3d64" \
-H "Content-Type: application/json" \
-d '{
"to": [{ "address": "jordan.rivera@example.com", "name": "Jordan Rivera" }],
"mailbox_id": "c5f1a8d3-6e2b-4c9f-b1a7-8d4e2c6f9b35",
"subject": "Your order is ready",
"html": "<p>Hi Jordan, your order is ready for pickup.</p>"
}'
{
"data": { "success": true },
"meta": { "request_id": "c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24" }
}
Check success
A well-formed request answers 200 even when nothing was sent, so always read
data.success. When it's false, data.error says why, for example that
the account has no sending domain. Fix the cause before you retry.
Send an Idempotency-Key header so a retry can't send twice. This route is
the only email route that accepts one. See
Retry safely with Idempotency-Key.
Merge a saved template
POST /email/public/email/merged-template fills a saved email template from
the recipient contact and sends it.
| Field | Type | Required | Description |
|---|---|---|---|
template_id |
string | Yes | An email template. Find yours with GET /template/public/templates?type=email. |
contact_id |
string | One of contact_id or to |
The contact whose fields fill the template |
to |
object[] | One of contact_id or to |
A recipient address. A contact is created if none matches. |
mailbox_id |
string | One of mailbox_id or from |
The mailbox to send from |
from |
object[] | One of mailbox_id or from |
One { "address", "name" } on a verified domain |
subject_override |
string | No | Use this subject instead of the template's |
merged_user_id |
string | No | The user whose details fill the template's sender fields, as the user_id from GET /user/public/users. Defaults to the key's user. |
preview |
string | No | Preview text |
cc, bcc |
object[] | No | More recipients |
send_type |
string | No | transactional (default) or bulk |
curl -X POST https://api-v2.dropcowboy.com/email/public/email/merged-template \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
-H "Content-Type: application/json" \
-d '{
"template_id": "f8a2c6e4-1d9b-4f3a-8c5e-7b1d3f9a2c46",
"contact_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"mailbox_id": "c5f1a8d3-6e2b-4c9f-b1a7-8d4e2c6f9b35",
"send_type": "bulk"
}'
The response is the same as Send an email: read
data.success. A template that doesn't exist answers 404. RCS templates
are refused.
List emails
GET /email/public/email returns sent and received emails, newest first.
| Field | Type | Required | Description |
|---|---|---|---|
contact_id |
string | No | Only this contact's emails |
direction |
string | No | inbound or outbound |
skip |
integer | No | How many to skip. Default 0. |
limit |
integer | No | How many to return. Default 50. |
include_total |
boolean | No | Also return total |
curl "https://api-v2.dropcowboy.com/email/public/email?contact_id=5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d&limit=10" \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"
{
"data": {
"emails": [
{
"email_id": "7a3e9c1f-5d2b-4f8a-9e6c-1b4d7f3a8c52",
"contact_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"mailbox_id": "c5f1a8d3-6e2b-4c9f-b1a7-8d4e2c6f9b35",
"template_id": null,
"direction": "outbound",
"send_type": "transactional",
"to": [{ "address": "jordan.rivera@example.com", "name": "Jordan Rivera" }],
"from": [{ "address": "hello@mail.example.com", "name": "Example Store" }],
"subject": "Your order is ready",
"created_at": 1774041600000,
"sent_at": 1774041602000
}
],
"total": 1
},
"meta": { "request_id": "c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24" }
}
Timestamps are epoch milliseconds. An email that a sending rule stopped has
state: "blocked" and a blocked_reason, such as too_many_bulk_emails.
List your mailboxes
GET /domain/public/mailboxes lists the mailboxes you can send from. Pass a
mailbox_id to the send routes, or as email_from_mailbox_id on an email
campaign.
curl https://api-v2.dropcowboy.com/domain/public/mailboxes \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"
{
"data": {
"mailboxes": [
{
"mailbox_id": "c5f1a8d3-6e2b-4c9f-b1a7-8d4e2c6f9b35",
"title": "Hello",
"address": "hello@mail.example.com",
"domain": "mail.example.com",
"display_name": "Example Dental",
"is_personal": false
}
]
},
"meta": { "request_id": "c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24" }
}
Results
Delivery, opens, clicks, bounces and complaints arrive on the
contact.email.status webhook. It can fire several times for one email.
status is delivered, opened, clicked, bounced or complained.
delivered fires once, when the receiving mail server accepts the email. An
email blocked before sending gets a single event with status: failure and the
reason_code that blocked it.
{
"event": "contact.email.status",
"data": {
"email_id": "7a3e9c1f-5d2b-4f8a-9e6c-1b4d7f3a8c52",
"contact_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"to": "jordan.rivera@example.com",
"from": "hello@mail.example.com",
"status": "bounced",
"reason": "hard_bounce",
"reason_code": 5030
}
}
If you keep your own suppression list, also subscribe to
contact.email.bounce_hard and contact.email.complaint. Email codes are the
5xxx rows in Outcomes. See Webhooks to
subscribe.
Email frequency limit
Email has its own per-contact limit, separate from the phone
contact frequency limit. By
default a contact can get 15 bulk emails in 3 days and 30 transactional emails
in 24 hours. Both can be changed for your account. An email over the limit is
blocked with too_many_bulk_emails (5019) or
too_many_transactional_emails (5020); send it later.
Addresses on your email test list, on the Dialing rules page, skip the limit, so you can send yourself test mail as often as you like.
Errors
| Status | Cause | What to do |
|---|---|---|
400 |
The request is malformed | Fix the fields. |
402 |
The account is past due | Update billing; see Payment required. |
403 |
The key lacks the scope, or your plan doesn't include email | Add the scope, or add email to your plan. The body is {"error", "message", "feature"}. |
404 |
The template doesn't exist (merged template only) | Check template_id. |
429 |
Too many requests | Back off and retry. |
For everything else, see Responses, errors and limits.
Common problems
successisfalse. Readdata.error. Most often the account has no verified sending domain or mailbox.- The email shows
state: "blocked". Checkblocked_reason. Fortoo_many_bulk_emailsortoo_many_transactional_emails, the contact hit the frequency limit; send later. - Merge fields came out empty. The recipient didn't match a contact with
those fields filled in. Send
contact_idwith Merge a saved template. - The email bounced (
5030). The address won't accept mail. Stop emailing it.
For more symptoms and fixes, see Troubleshooting.
While Drop Cowboy provides tools to support compliance efforts, customers remain solely responsible for obtaining proper consent, maintaining opt-out lists, and complying with all federal and state telemarketing regulations. Consult with your legal counsel to ensure your specific use case and consent mechanisms comply with applicable laws.