Email
Write, schedule and track emails that keep customers in the loop.
Email API
Lists, contacts, campaigns and automations from your own code: add a contact when someone signs up, tag them, read a report.
Getting started
Three requests cover most integrations: add a contact, tag them, read a report.
Base URL and authentication
https://enoughstack-app-7r9ib.ondigitalocean.app/api/public/v1 Authorization: Bearer ms_live_… # create a key under API keys
Add a contact when someone signs up
curl -X POST https://enoughstack-app-7r9ib.ondigitalocean.app/api/public/v1/lists/lst_audience/contacts \
-H "Authorization: Bearer $ENOUGHSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email_address": "maya@northwind.studio",
"fields": {"FirstName": "Maya", "Plan": "Team"},
"tags": ["customer", "team"]}'Posting the same address again updates the contact instead of failing, so you can call this from a sign-up handler without checking first.
Read a campaign report
curl https://enoughstack-app-7r9ib.ondigitalocean.app/api/public/v1/campaigns/cmp_004/report \
-H "Authorization: Bearer $ENOUGHSTACK_API_KEY"
{
"campaign_id": "cmp_004",
"stats": { "recipients": 166, "delivered": 165, "opens_unique": 93, "open_rate": 0.5636, ... },
"links": [{ "url": "https://framecast.app", "unique_clicks": 27, "total_clicks": 35 }]
}Lists
- GET
/listsYour lists with subscriber countsread - GET
/lists/{list_id}One listread - GET
/lists/{list_id}/tagsTags on a listread - GET
/lists/{list_id}/segmentsSegments on a listread
Contacts
- GET
/lists/{list_id}/contactsPage through contacts; filter by status or tagread - POST
/lists/{list_id}/contactsCreate a contact, or update the one with that emailwrite - GET
/lists/{list_id}/contacts/{id_or_email}One contact, by id or email addressread - PATCH
/lists/{list_id}/contacts/{contact_id}Change fields, tags or statuswrite - DELETE
/lists/{list_id}/contacts/{contact_id}Remove a contactwrite - GET
/lists/{list_id}/segments/{segment_id}/contactsContacts matching a segmentread
Campaigns
- GET
/campaignsCampaigns, optionally by statusread - GET
/campaigns/{campaign_id}One campaign with its statsread - GET
/campaigns/{campaign_id}/reportDelivery stats and link clicksread
Automations
- GET
/automationsYour automationsread - POST
/automations/{automation_id}/enrollStart an automation for a contactwrite
API keys
Every request carries a key with the read or write scope. Keys are created under Developers and shown once.
Interactive docs
Every endpoint with request and response schemas, generated from the API itself. Use “Authorize” with one of your keys to try requests.
Open API docsConventions
- Errors
- Always
{"error": {"code", "message"}}. Codes includeunauthenticated,insufficient_scope,not_found,validation_error. - Pagination
- Lists return
paging.next; pass it back asstarting_after. Up to 100 items per page. - Webhooks
- Each delivery carries
X-EnoughStack-Signature: sha256=…, an HMAC of the raw body with the endpoint secret. Respond 2xx within 10 seconds. - Versioning
- The version is in the path (
/v1). Fields are only ever added.
Webhook payload
POST https://your-app.example/hooks
X-EnoughStack-Signature: sha256=…
{
"event": "contact.created",
"created_at": "2026-10-06T09:14:02Z",
"data": {
"id": "ct_0231",
"list_id": "lst_audience",
"email_address": "maya@northwind.studio",
"status": "subscribed",
"tags": ["customer", "team"]
}
}