EnoughStack
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.

Manage API keys

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 docs

Conventions

Errors
Always {"error": {"code", "message"}}. Codes include unauthenticated, insufficient_scope, not_found, validation_error.
Pagination
Lists return paging.next; pass it back as starting_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"]
  }
}