For Coding Agents
Create an API Key, then give your AI coding agent this page — it will have everything it needs to build TopMail integrations.
Get an API key
Create an API key in your workspace settings. Your key will start with tm_live_ for production or tm_test_ for sandbox mode.
Share this link with your coding agent
Paste this URL into your conversation with Claude, Codex, Gemini, or any AI coding assistant. The agent will read this page and have full context to write TopMail API calls, handle errors, and build multi-step workflows autonomously.
https://api.topmail.so/developers/coding-agentsThat's it. No configuration files, no copy-pasting blocks. Your agent reads the page and does the rest.
API Reference
Complete reference for all TopMail API resources. This is the context your coding agent uses when it reads this page.
# TopMail API Reference
## Base URL
https://api.topmail.so/api/v1
## OpenAPI Spec
https://api.topmail.so/openapi.yaml
## Authentication
All requests require a Bearer token:
Authorization: Bearer tm_live_<your_api_key>
Sandbox/test keys use prefix: tm_test_<key> (skips real email sending, returns mock results)
## TypeScript SDK
npm install @topmail/sdk
import { TopMail } from '@topmail/sdk';
const topmail = new TopMail('tm_live_<your_api_key>');
## Resources
### Contacts
- GET /contacts — List contacts (search, subscribed, limit, offset)
- POST /contacts — Create contact (email*, first_name, last_name, attributes, subscribed, list_id)
- GET /contacts/:id — Get contact with list memberships
- PATCH /contacts/:id — Update contact (partial update, attributes merge)
- DELETE /contacts/:id — Delete contact
Example response (GET /contacts/:id):
{
"data": {
"id": "uuid",
"email": "user@example.com",
"first_name": "Jane",
"last_name": "Doe",
"attributes": { "plan": "pro", "company": "Acme" },
"subscribed": true,
"created_at": "2025-01-15T10:30:00.000Z",
"lists": [{ "id": "uuid", "name": "Newsletter" }]
}
}
### Lists
- GET /lists — List all lists (type, limit, offset)
- POST /lists — Create list (name*, description, type, conditions)
- GET /lists/:id — Get list with member count
- PATCH /lists/:id — Update list
- DELETE /lists/:id — Delete list (members preserved)
- GET /lists/:id/members — List members (limit, offset)
- POST /lists/:id/members — Add members { contact_ids: ["uuid", ...] }
- DELETE /lists/:id/members — Remove members { contact_ids: ["uuid", ...] }
### Segments
- GET /segments — List segments (search, limit, offset)
- POST /segments — Create segment (name*, conditions)
- GET /segments/:id — Get segment
- PATCH /segments/:id — Update segment
- DELETE /segments/:id — Delete segment
- POST /segments/:id/estimate — Estimate matching contacts count
- GET /segments/:id/contacts — List matching contacts (limit, offset)
Example: Create segment with conditions:
POST /segments { "name": "Active Buyers", "conditions": { "match": "all", "rules": [{ "field": "subscribed", "operator": "equals", "value": true }] } }
### Tags
- GET /tags — List tags with contact_count (search, limit, offset)
- POST /tags — Create tag (name*, color, description)
- GET /tags/:id — Get tag
- PATCH /tags/:id — Update tag
- DELETE /tags/:id — Delete tag (cascade removes assignments)
- GET /tags/:id/contacts — List tagged contacts
- POST /tags/:id/contacts — Assign tag { contact_ids: ["uuid", ...] }
- DELETE /tags/:id/contacts — Remove tag { contact_ids: ["uuid", ...] }
### Automations
- POST /automations — Create automation (name*, trigger_type*, trigger_config, steps[])
- GET /automations — List automations (status, limit, offset)
- GET /automations/:id — Get automation with steps and run stats
- PATCH /automations/:id — Update name/status (draft→active, active↔paused)
- DELETE /automations/:id — Soft delete automation
- POST /automations/:id/steps — Add step (step_type*, config, position)
- PUT /automations/:id/steps — Batch update steps (reorder/update configs)
- PATCH /automations/:id/steps/:stepId — Update step type or config
- DELETE /automations/:id/steps/:stepId — Remove step (reorders remaining)
- POST /automations/:id/trigger — Trigger for contact (contact_id or email, trigger_data)
- GET /automations/:id/runs — List runs (status filter: active|completed|failed|paused|cancelled, limit, offset)
- POST /automations/:id/runs/:runId/cancel — Cancel an active or paused run
Step types and their config fields (defaults provided if config omitted):
email — default: { templateId: null, subject: "", previewText: "" }
templateId (string|null): Template ID to use for email content
subject (string): Email subject line
previewText (string): Preview text shown in inbox
delay — default: { duration: 1, unit: "days" }
duration (number): How long to wait
unit (string): "minutes", "hours", "days", or "weeks"
condition — default: { field: "", operator: "equals", value: "" }
field (string): Contact field to evaluate (e.g. "email", "first_name", or custom attribute name)
operator (string): "equals", "not_equals", "contains", "gt", "lt", "gte", or "lte"
value (string|number|boolean): Value to compare against
trueBranchStepId (string|null): Step ID to jump to if true
falseBranchStepId (string|null): Step ID to jump to if false
tag — default: { action: "add", tagName: "" }
action (string): "add" or "remove"
tagName (string): Name of the tag
add_to_list / remove_from_list — default: { listId: null }
listId (string): List ID to add/remove the contact from
update_attribute — default: { attributeName: "", attributeValue: "" }
attributeName (string): Contact attribute name to update
attributeValue (string): Value to set
webhook — default: { url: "", method: "POST", headers: {} }
url (string): Webhook URL
method (string): "GET", "POST", "PUT", or "PATCH"
headers (object): Custom headers as key-value pairs
body (object|string): Request body
wait_for_event — default: { eventType: "email_opened", timeoutDays: 7, timeoutAction: "continue" }
eventType (string): "email_opened", "email_clicked", "purchase", or "page_viewed"
timeoutDays (number): Days to wait before timing out
timeoutAction (string): "continue" (next step), "skip" (skip next), or "branch" (jump to timeoutBranch)
move_to_flow — default: { targetFlowId: "", exitCurrentFlow: true }
targetFlowId (string): Target automation ID
exitCurrentFlow (boolean): true = exit current, false = run both in parallel
### Campaigns (read-only)
- GET /campaigns — List campaigns (status, limit, offset)
- GET /campaigns/:id — Get campaign with stats and rates
### Templates
- GET /templates — List templates
- POST /templates — Create template (name*, html*, description)
- GET /templates/:id — Get template with HTML content
### Email
- POST /email/send — Send transactional email (to*, subject*, html*, from_email, from_name, reply_to)
- POST /email/batch — Send batch emails (messages[])
- GET /email/:messageId — Get delivery status
## Sending Email — What You Need
To send email via the API, you need:
- from_name: display name shown to recipients
- from_email: used as the Reply-To address (replies go back here)
- The recipient (to), subject, and body (html or text)
No domain setup is needed to start. Every workspace has a shared sending domain. Emails are sent
from the shared domain (e.g., nick@abc123.send.topmail-manage.com), and from_email is
used as Reply-To so replies reach the user.
To send directly from a custom domain, use the Domains API to add and verify one:
POST /domains → add DNS records → POST /domains/:id/verify → send with from_email on that domain.
Minimal send example:
POST /email/send { "to": "recipient@example.com", "subject": "Hello!", "html": "<h1>Hi there</h1>", "from_name": "Nico Jareck", "from_email": "nick@mybrand.com" }
### Suppressions
- GET /suppressions — List suppressed emails (limit, offset)
- POST /suppressions — Add to suppression list (email*, reason)
- DELETE /suppressions — Remove from suppression list (email*)
### Domains
- GET /domains — List sending domains
- POST /domains — Add a domain (returns DNS records to configure)
- GET /domains/:id — Get domain with verification status
- POST /domains/:id/verify — Check verification status (call after adding DNS records)
- DELETE /domains/:id — Remove a domain
Domain setup flow:
1. POST /domains with { "domain": "mybrand.com" } → returns DNS records (DKIM CNAMEs + verification TXT)
2. User adds DNS records at their DNS provider
3. POST /domains/:id/verify → checks DNS propagation and SES verification
4. Once verified=true, use from_email on that domain when sending emails
### Webhooks
- POST /webhooks — Subscribe (url*, events[])
- DELETE /webhooks/:id — Unsubscribe
- POST /webhooks/:id/test — Send test event
Webhook event types: email.sent, email.delivered, email.bounced, email.complained, email.opened, email.clicked, contact.subscribed, contact.unsubscribed
### Tracking
- POST /track/product-view — Track product view (contact_email*, product_id*, product data)
### Conversions
- GET /conversions — List conversions (limit, offset)
- POST /conversions — Create conversion (contact_id*, value*, currency, metadata)
### Health
- GET /health — API health check (no auth required)
## Response Format
Single resource: { "data": { ... } }
List resource: { "data": [...], "pagination": { "total": 150, "limit": 100, "offset": 0, "has_more": true } }
## Error Format
{ "error": { "code": "error_code", "message": "Human-readable message" } }
Common codes: validation_error (400), unauthorized (401), not_found (404), conflict (409), rate_limit_exceeded (429), internal_error (500)
## Pagination
Use limit (default: 100, max: 1000) and offset (default: 0) query parameters.
## Idempotency
POST /email/send and /email/batch support Idempotency-Key header to prevent duplicate sends.
## SDK Methods Quick Reference
topmail.contacts.list/get/create/update/delete
topmail.lists.list/get/create/update/delete + topmail.lists.members.add/remove/list
topmail.segments.list/get/create/update/delete/estimateCount/listContacts
topmail.tags.list/get/create/update/delete + topmail.tags.contacts.assign/remove
topmail.automations.list/get/update/activate/pause/trigger/listRuns/cancelRun
topmail.campaigns.list/get
topmail.templates.list/get/create
topmail.email.send/sendBatch/getStatus
topmail.domains.list/get/create/verify/delete
topmail.suppressions.list/add/remove
topmail.webhooks.create/delete/testExample Workflows
Here are multi-step workflows your coding agent can build using the TopMail SDK.
Create a segment and estimate audience
# Step 1: Create a segmentcurl -X POST https://api.topmail.so/api/v1/segments \-H "Authorization: Bearer tm_live_abc123" \-H "Content-Type: application/json" \-d '{"name": "Active Buyers","conditions": {"match": "all","rules": [{"field": "revenue_potential_score", "operator": "gte", "value": 60},{"field": "subscribed", "operator": "equals", "value": true}]}}'# Step 2: Estimate the audience sizecurl -X POST https://api.topmail.so/api/v1/segments/SEGMENT_ID/estimate \-H "Authorization: Bearer tm_live_abc123"
Tag contacts and trigger an automation
# Step 1: Create a tagcurl -X POST https://api.topmail.so/api/v1/tags \-H "Authorization: Bearer tm_live_abc123" \-H "Content-Type: application/json" \-d '{"name": "Upgrade Candidate", "color": "#f59e0b"}'# Step 2: Assign the tag to contactscurl -X POST https://api.topmail.so/api/v1/tags/TAG_ID/contacts \-H "Authorization: Bearer tm_live_abc123" \-H "Content-Type: application/json" \-d '{"contact_ids": ["CONTACT_ID_1", "CONTACT_ID_2"]}'# Step 3: Trigger an automation for each contactcurl -X POST https://api.topmail.so/api/v1/automations/AUTOMATION_ID/trigger \-H "Authorization: Bearer tm_live_abc123" \-H "Content-Type: application/json" \-d '{"contact_id": "CONTACT_ID_1", "trigger_data": {"reason": "upgrade_candidate"}}'
Using the SDK
We recommend using the official TypeScript SDK for agent integrations. It provides full type safety, built-in error handling, and pagination helpers that make it easier for your coding agent to write correct API calls on the first try.
View SDK Documentation