API
API endpoints
API endpoints are versioned together. Select an API version to jump to its section. All endpoint content is present in this HTML page.
API v1 Stable
v1 endpoints
GET
Stable
Get quota status
/v1/quota
Use this endpoint when an app or coding agent needs to check whether the current environment can keep sending. The response is operational state, not billing authority.
Request
Name
Type
Required
Description
Example
Authorization
string
Yes
Bearer API key for the EmailsDone environment.
Bearer ed_...
curl
Copy
curl -X GET 'https://api.emailsdone.dev/v1/quota' \
-H 'Authorization: Bearer ed_...'
Response
Name
Type
Required
Description
Example
ok
boolean
Yes
True when quota was read.
true
quota.tenantId
string
Yes
Tenant identifier.
tenant_123
quota.projectId
string
Yes
Project identifier.
project_123
quota.environmentId
string
Yes
Environment identifier.
production
quota.planId
string
Yes
Plan id from the tenant limits snapshot.
build
quota.accessStatus
string
Yes
Billing access state.
active
quota.sendingStatus
string
Yes
Environment sending status.
active
quota.period.id
string
Yes
Current usage period id.
2026-05
quota.monthly.limit
number
Yes
Monthly included email limit.
1000
quota.monthly.used
number
Yes
Tenant emails sent in the current month.
42
quota.monthly.remaining
number
Yes
Remaining included emails for the month.
958
quota.monthly.status
string
Yes
Quota availability.
available
quota.environment.usedThisMonth
number
Yes
Environment emails sent in the current month.
42
Copy
{
"ok": true,
"quota": {
"tenantId": "tenant_123",
"projectId": "project_123",
"environmentId": "production",
"planId": "build",
"accessStatus": "active",
"sendingStatus": "active",
"period": {
"id": "2026-05",
"startsAt": "2026-05-01T00:00:00.000Z",
"endsAt": "2026-06-01T00:00:00.000Z"
},
"monthly": {
"limit": 1000,
"used": 42,
"remaining": 958,
"overageAllowed": false,
"overageEmails": 0,
"status": "available"
},
"environment": {
"usedThisMonth": 42
}
}
}
Errors
Status Code Message
401
missing_api_key
Authorization bearer token is missing.
401
invalid_api_key
API key is invalid.
402
trial_expired
The trial can no longer send email.
403
api_key_revoked
API key has been revoked.
403
tenant_not_found
Tenant not found for API key.
403
project_inactive
Project is disabled by the current plan.
403
environment_inactive
Environment is disabled by the current plan.
404
project_not_found
Project not found for API key.
404
environment_not_found
Environment not found for API key.
405
method_not_allowed
Only GET is allowed.
500
internal
Unexpected server error.
Notes
Use this for operational status in dashboards and agent checks. Do not use the client response as billing authority. Entitlements are backend-owned.
POST
Stable
Get contact
/v1/contacts/get
Reads tenant-level contact metadata and delivery summary.
Request
Name
Type
Required
Description
Example
emailOrContactId
string
Yes
Contact email or contact id.
user@example.com
curl
Copy
curl -X POST 'https://api.emailsdone.dev/v1/contacts/get' \
-H 'Authorization: Bearer ed_...' \
-H 'Content-Type: application/json' \
-d '{}'
Response
Name
Type
Required
Description
Example
ok
boolean
Yes
True when contact was read.
contact
object
Yes
Contact record.
Errors
Status Code Message
400
invalid_request
Request body failed validation.
401
missing_api_key
Authorization bearer token is missing.
401
invalid_api_key
API key is invalid.
403
api_key_revoked
API key has been revoked.
404
contact_not_found
Contact was not found.
405
method_not_allowed
Only POST is allowed.
500
internal
Unexpected server error.
Notes
Contacts are tenant-level recipients for app email history, not CRM records. Existing environment API keys can access tenant contacts endpoints.
POST
Stable
Add contacts to group
/v1/contacts/groups/add
Creates the group if needed.
Request
Name
Type
Required
Description
Example
groupSlug
string
Yes
Group slug.
contactIds
array
Yes
Contact ids.
curl
Copy
curl -X POST 'https://api.emailsdone.dev/v1/contacts/groups/add' \
-H 'Authorization: Bearer ed_...' \
-H 'Content-Type: application/json' \
-d '{}'
Response
Name
Type
Required
Description
Example
ok
boolean
Yes
True when membership was updated.
updated
number
No
Contacts updated.
Errors
Status Code Message
400
invalid_request
Request body failed validation.
401
missing_api_key
Authorization bearer token is missing.
401
invalid_api_key
API key is invalid.
403
api_key_revoked
API key has been revoked.
404
contact_not_found
Contact was not found.
405
method_not_allowed
Only POST is allowed.
500
internal
Unexpected server error.
Notes
Contacts are tenant-level recipients for app email history, not CRM records. Existing environment API keys can access tenant contacts endpoints.
POST
Stable
Delete contact group
/v1/contacts/groups/delete
Deleting a group does not delete contacts.
Request
Name
Type
Required
Description
Example
groupSlug
string
Yes
Group slug.
beta-users
curl
Copy
curl -X POST 'https://api.emailsdone.dev/v1/contacts/groups/delete' \
-H 'Authorization: Bearer ed_...' \
-H 'Content-Type: application/json' \
-d '{}'
Response
Name
Type
Required
Description
Example
ok
boolean
Yes
True when group was deleted.
Errors
Status Code Message
400
invalid_request
Request body failed validation.
401
missing_api_key
Authorization bearer token is missing.
401
invalid_api_key
API key is invalid.
403
api_key_revoked
API key has been revoked.
404
contact_not_found
Contact was not found.
405
method_not_allowed
Only POST is allowed.
500
internal
Unexpected server error.
Notes
Contacts are tenant-level recipients for app email history, not CRM records. Existing environment API keys can access tenant contacts endpoints.
POST
Stable
List contact groups
/v1/contacts/groups/list
Groups are simple labels/lists, not dynamic CRM segments.
Request
No fields.
curl
Copy
curl -X POST 'https://api.emailsdone.dev/v1/contacts/groups/list' \
-H 'Authorization: Bearer ed_...' \
-H 'Content-Type: application/json' \
-d '{}'
Response
Name
Type
Required
Description
Example
ok
boolean
Yes
True when groups were read.
groups
array
Yes
Contact groups.
Errors
Status Code Message
400
invalid_request
Request body failed validation.
401
missing_api_key
Authorization bearer token is missing.
401
invalid_api_key
API key is invalid.
403
api_key_revoked
API key has been revoked.
404
contact_not_found
Contact was not found.
405
method_not_allowed
Only POST is allowed.
500
internal
Unexpected server error.
Notes
Contacts are tenant-level recipients for app email history, not CRM records. Existing environment API keys can access tenant contacts endpoints.
POST
Stable
Remove contacts from group
/v1/contacts/groups/remove
Does not delete contacts.
Request
Name
Type
Required
Description
Example
groupSlug
string
Yes
Group slug.
contactIds
array
Yes
Contact ids.
curl
Copy
curl -X POST 'https://api.emailsdone.dev/v1/contacts/groups/remove' \
-H 'Authorization: Bearer ed_...' \
-H 'Content-Type: application/json' \
-d '{}'
Response
Name
Type
Required
Description
Example
ok
boolean
Yes
True when membership was updated.
updated
number
No
Contacts updated.
Errors
Status Code Message
400
invalid_request
Request body failed validation.
401
missing_api_key
Authorization bearer token is missing.
401
invalid_api_key
API key is invalid.
403
api_key_revoked
API key has been revoked.
404
contact_not_found
Contact was not found.
405
method_not_allowed
Only POST is allowed.
500
internal
Unexpected server error.
Notes
Contacts are tenant-level recipients for app email history, not CRM records. Existing environment API keys can access tenant contacts endpoints.
POST
Stable
Create or update contact group
/v1/contacts/groups/upsert
Group slug is stable in V1; display name can be updated.
Request
Name
Type
Required
Description
Example
name
string
Yes
Group display name.
Beta users
slug
string
No
Stable group slug.
beta-users
curl
Copy
curl -X POST 'https://api.emailsdone.dev/v1/contacts/groups/upsert' \
-H 'Authorization: Bearer ed_...' \
-H 'Content-Type: application/json' \
-d '{}'
Response
Name
Type
Required
Description
Example
ok
boolean
Yes
True when group was saved.
group
object
Yes
Contact group.
Errors
Status Code Message
400
invalid_request
Request body failed validation.
401
missing_api_key
Authorization bearer token is missing.
401
invalid_api_key
API key is invalid.
403
api_key_revoked
API key has been revoked.
404
contact_not_found
Contact was not found.
405
method_not_allowed
Only POST is allowed.
500
internal
Unexpected server error.
Notes
Contacts are tenant-level recipients for app email history, not CRM records. Existing environment API keys can access tenant contacts endpoints.
POST
Stable
List contacts
/v1/contacts/list
Supports search and simple AND-style filter clauses such as status, group, project, environment, last emailed, and suppression flags.
Request
Name
Type
Required
Description
Example
search
string
No
Search email or name.
filters
array
No
Simple contact filter clauses.
pageSize
number
No
Page size, capped by the API.
curl
Copy
curl -X POST 'https://api.emailsdone.dev/v1/contacts/list' \
-H 'Authorization: Bearer ed_...' \
-H 'Content-Type: application/json' \
-d '{}'
Response
Name
Type
Required
Description
Example
ok
boolean
Yes
True when contacts were read.
contacts
array
Yes
Contact records.
Errors
Status Code Message
400
invalid_request
Request body failed validation.
401
missing_api_key
Authorization bearer token is missing.
401
invalid_api_key
API key is invalid.
403
api_key_revoked
API key has been revoked.
404
contact_not_found
Contact was not found.
405
method_not_allowed
Only POST is allowed.
500
internal
Unexpected server error.
Notes
Contacts are tenant-level recipients for app email history, not CRM records. Existing environment API keys can access tenant contacts endpoints.
POST
Stable
Send template to contacts
/v1/contacts/send
Uses the existing EmailsDone queue and send-permission checks. This is for operational app notifications, not arbitrary HTML campaigns.
Request
Name
Type
Required
Description
Example
projectId
string
Yes
Project id.
environmentId
string
Yes
Environment id.
templateId
string
Yes
Template id.
templateData
object
Yes
Template data.
contactIds
array
No
Selected contact ids.
groupSlug
string
No
Group slug.
curl
Copy
curl -X POST 'https://api.emailsdone.dev/v1/contacts/send' \
-H 'Authorization: Bearer ed_...' \
-H 'Content-Type: application/json' \
-d '{}'
Response
Name
Type
Required
Description
Example
ok
boolean
Yes
True when job was queued.
jobId
string
Yes
Bulk send job id.
queued
number
Yes
Messages queued.
Errors
Status Code Message
400
invalid_request
Request body failed validation.
401
missing_api_key
Authorization bearer token is missing.
401
invalid_api_key
API key is invalid.
403
api_key_revoked
API key has been revoked.
404
contact_not_found
Contact was not found.
405
method_not_allowed
Only POST is allowed.
500
internal
Unexpected server error.
Notes
Contacts are tenant-level recipients for app email history, not CRM records. Existing environment API keys can access tenant contacts endpoints.
POST
Stable
Dry run contact send
/v1/contacts/send/dry-run
Evaluates template data and existing send-permission rules before enqueueing.
Request
Name
Type
Required
Description
Example
projectId
string
Yes
Project id.
environmentId
string
Yes
Environment id.
templateId
string
Yes
Template id.
templateData
object
Yes
Template data.
contactIds
array
No
Selected contact ids.
groupSlug
string
No
Group slug.
curl
Copy
curl -X POST 'https://api.emailsdone.dev/v1/contacts/send/dry-run' \
-H 'Authorization: Bearer ed_...' \
-H 'Content-Type: application/json' \
-d '{}'
Response
Name
Type
Required
Description
Example
ok
boolean
Yes
True when dry run completed.
eligible
number
Yes
Eligible recipients.
skipped
object
Yes
Skip counts by reason.
Errors
Status Code Message
400
invalid_request
Request body failed validation.
401
missing_api_key
Authorization bearer token is missing.
401
invalid_api_key
API key is invalid.
403
api_key_revoked
API key has been revoked.
404
contact_not_found
Contact was not found.
405
method_not_allowed
Only POST is allowed.
500
internal
Unexpected server error.
Notes
Contacts are tenant-level recipients for app email history, not CRM records. Existing environment API keys can access tenant contacts endpoints.
POST
Stable
Create or update contact
/v1/contacts/upsert
Creates or updates a lightweight tenant-level contact while preserving EmailsDone's template-first sending model.
Request
Name
Type
Required
Description
Example
email
email
Yes
Contact email address.
user@example.com
name
string
No
Optional contact name.
Ada Lovelace
groupSlugs
array
No
Simple tenant-level group slugs.
["beta-users"]
customFields
object
No
Flat string, number, boolean, or null fields.
{"plan":"build"}
curl
Copy
curl -X POST 'https://api.emailsdone.dev/v1/contacts/upsert' \
-H 'Authorization: Bearer ed_...' \
-H 'Content-Type: application/json' \
-d '{
"email": "user@example.com",
"name": "Ada Lovelace",
"groupSlugs": [
"beta-users"
]
}'
Response
Name
Type
Required
Description
Example
ok
boolean
Yes
True when contact was saved.
true
contact
object
Yes
Contact record.
{}
Errors
Status Code Message
400
invalid_request
Request body failed validation.
401
missing_api_key
Authorization bearer token is missing.
401
invalid_api_key
API key is invalid.
403
api_key_revoked
API key has been revoked.
404
contact_not_found
Contact was not found.
405
method_not_allowed
Only POST is allowed.
500
internal
Unexpected server error.
Notes
Contacts are tenant-level recipients for app email history, not CRM records. Existing environment API keys can access tenant contacts endpoints.
POST
Stable
Resubscribe a recipient
/v1/recipients/resubscribe
Use this endpoint only when the recipient has explicitly chosen to receive app notifications again. It writes an audit event and clears the app notification unsubscribe state for the API key environment.
Request
Name
Type
Required
Description
Example
Authorization
string
Yes
Bearer API key for the EmailsDone environment.
Bearer ed_...
email
email
Yes
Recipient email address to resubscribe.
user@example.com
scope
string
Yes
Subscription scope. V1 supports app notifications.
app_notifications
curl
Copy
curl -X POST 'https://api.emailsdone.dev/v1/recipients/resubscribe' \
-H 'Authorization: Bearer ed_...' \
-H 'Content-Type: application/json' \
-d '{
"email": "user@example.com",
"scope": "app_notifications"
}'
Response
Name
Type
Required
Description
Example
ok
boolean
Yes
True when the recipient was marked as resubscribed.
true
Errors
Status Code Message
400
Invalid resubscribe request.
Expected a valid email and app_notifications scope.
401
missing_api_key
Authorization bearer token is missing.
401
invalid_api_key
API key is invalid.
402
trial_expired
The trial can no longer send email.
403
api_key_revoked
API key has been revoked.
405
method_not_allowed
Only POST is allowed.
500
internal
Unexpected server error.
Notes
Call this after an explicit user action, not automatically after a bounce or complaint. The only V1 scope is app_notifications.
POST
Stable
Get recipient status
/v1/recipients/status
Use this endpoint to inspect whether an app can send notifications to a recipient and to display recent delivery state without exposing raw message bodies.
Request
Name
Type
Required
Description
Example
Authorization
string
Yes
Bearer API key for the EmailsDone environment.
Bearer ed_...
email
email
Yes
Recipient email address to inspect.
user@example.com
limit
number
No
Number of recent messages to return. Values are clamped between 1 and 50.
10
curl
Copy
curl -X POST 'https://api.emailsdone.dev/v1/recipients/status' \
-H 'Authorization: Bearer ed_...' \
-H 'Content-Type: application/json' \
-d '{
"email": "user@example.com",
"limit": 10
}'
Response
Name
Type
Required
Description
Example
ok
boolean
Yes
True when recipient status was read.
true
recipient.emailMasked
string
Yes
Masked recipient email.
u***@example.com
recipient.recipientDomain
string
Yes
Recipient domain.
example.com
recipient.state
string
Yes
Recipient delivery/subscription state.
clear
recipient.subscription.status
string
Yes
Notification subscription status.
subscribed
recipient.delivery.hardBounceCount
number
Yes
Hard bounce count for this recipient.
0
recipient.delivery.complaintCount
number
Yes
Complaint count for this recipient.
0
recipient.canSendNotifications
boolean
Yes
Whether notification templates can currently be sent.
true
messages
array
Yes
Recent message metadata for this recipient.
[]
Copy
{
"ok": true,
"recipient": {
"emailMasked": "u***@example.com",
"recipientDomain": "example.com",
"state": "clear",
"subscription": {
"scope": "app_notifications",
"status": "subscribed",
"unsubscribedAt": null,
"resubscribedAt": null
},
"delivery": {
"hardBounceCount": 0,
"complaintCount": 0,
"cooldownUntil": null,
"complainedAt": null
},
"canSendNotifications": true
},
"messages": []
}
Errors
Status Code Message
400
invalid_request
Expected a valid recipient email.
401
missing_api_key
Authorization bearer token is missing.
401
invalid_api_key
API key is invalid.
402
trial_expired
The trial can no longer send email.
403
api_key_revoked
API key has been revoked.
403
project_inactive
Project is disabled by the current plan.
403
environment_inactive
Environment is disabled by the current plan.
404
project_not_found
Project not found for API key.
404
environment_not_found
Environment not found for API key.
405
method_not_allowed
Only POST is allowed.
500
internal
Unexpected server error.
Notes
This endpoint returns metadata only. It does not expose email content. Use canSendNotifications before showing notification-specific controls.
POST
Stable
Send a transactional email
/v1/send
Use this endpoint for password resets, verification emails, app notifications, billing messages, and other transactional events. A successful response means the message has been accepted and queued, not that SES has already delivered it.
Request
Name
Type
Required
Description
Example
Authorization
string
Yes
Bearer API key for the EmailsDone environment.
Bearer ed_...
Idempotency-Key
string
No
Optional retry key. Reusing the same key returns the original accepted message for 24 hours.Max 120 characters
password-reset-user-123-2026-05-26
templateId
string
Yes
Template identifier, for example password-reset or verify-email.
password-reset
to
email
Yes
Recipient email address.
user@example.com
templateVersion
string
No
Optional template version such as v1. If omitted, EmailsDone uses the latest published version for the template.
v1
from
email
No
Optional From address. For custom sending domains, the domain must match the effective project or environment sending domain. If omitted, EmailsDone uses the environment From Email setting, then the project From Email setting, then notifications@domain.
notifications@example.com
fromName
string
No
Optional display name for the From header. If omitted, EmailsDone uses the environment setting, then the project setting.Max 120 characters
Acme App
replyTo
email
No
Optional Reply To email address. If omitted, EmailsDone uses the environment setting, then the project setting.
support@example.com
data
object
Yes
Template data. Required and optional fields are derived from the selected template blocks.
{"actionButton":{"label":"Reset password","url":"https://app.example.com/reset/token"}}
curl
Copy
curl -X POST 'https://api.emailsdone.dev/v1/send' \
-H 'Authorization: Bearer ed_...' \
-H 'Content-Type: application/json' \
-d '{
"templateId": "password-reset",
"templateVersion": "v1",
"to": "user@example.com",
"from": "notifications@example.com",
"fromName": "Acme App",
"replyTo": "support@example.com",
"data": {
"actionButton": {
"label": "Reset password",
"url": "https://app.example.com/reset/token"
}
}
}'
Response
Name
Type
Required
Description
Example
ok
boolean
Yes
True when the request was accepted.
true
status
string
Yes
Message acceptance state.
accepted
messageId
string
Yes
Message record identifier for later operational lookup.
msg_abc123
idempotent
boolean
Yes
True when the response is for an earlier request with the same Idempotency-Key.
false
Copy
{
"ok": true,
"status": "accepted",
"messageId": "msg_abc123",
"idempotent": false
}
Errors
Status Code Message
400
invalid_request
Expected templateId, to, and data.
400
invalid_from_address
From address failed validation.
400
invalid_from_name
From name failed validation.
400
invalid_reply_to
Reply To failed validation.
400
invalid_template_data
Template data failed validation.
401
missing_api_key
Authorization bearer token is missing.
401
invalid_api_key
API key is invalid.
402
trial_expired
The trial can no longer send email.
403
api_key_revoked
API key has been revoked.
403
monthly_quota_exceeded
Tenant has exhausted the monthly quota.
403
app_paused
Sending is paused for the app.
403
app_blocked
Sending is blocked for the app.
403
recipient_complained
Recipient has complained.
403
recipient_unsubscribed
Recipient is unsubscribed from notifications.
403
sending_domain_not_ready
The configured sending domain is not verified yet.
403
from_domain_not_allowed
From address does not match the configured sending domain.
404
project_not_found
Project not found for API key.
404
environment_not_found
Environment not found for API key.
404
template_not_found
Template was not found.
404
template_version_not_found
Requested template version was not found.
405
method_not_allowed
Only POST is allowed.
500
internal
Unexpected server error.
Notes
Use Idempotency-Key for password resets, billing emails, and other retry-prone workflows. Do not send HTML. Choose a templateId and pass the required template data. Treat accepted as queued. Delivery and bounce state are recorded asynchronously.