API reference
Base URL:
https://app.mailstein.com/api/v1
Every request needs Authorization: Bearer ms_.... See
authentication.
This page is generated from the OpenAPI description the running server
publishes, so it describes what is deployed rather than what was intended. The
specification itself is at /openapi.json.
Emails
get/v1/emails/{emailId}
Path parameters
| emailId required | string |
patch/v1/emails/{emailId}
Path parameters
| emailId required | string |
Body
| scheduledAt required | string |
get/v1/emails
Query parameters
| page | string |
| limit | string |
| startDate | string |
| endDate | string |
| domainId | string | string[] |
post/v1/emails
Body
| to required | string | string[] | |
| from required | string | |
| subject | string | Optional when templateId is provided |
| templateId | string | ID of a template from the dashboard |
| variables | object | |
| replyTo | string | string[] | |
| cc | string | string[] | |
| bcc | string | string[] | |
| text | string | |
| html | string | |
| headers | object | Custom headers to included with the emails |
| attachments | object[] | |
| scheduledAt | string | |
| inReplyToId | string |
post/v1/emails/batch
post/v1/emails/{emailId}/cancel
Path parameters
| emailId required | string |
Domains
get/v1/domains
post/v1/domains
Body
| name required | string |
| region required | string |
put/v1/domains/{id}/verify
Path parameters
| id | number |
get/v1/domains/{id}
Path parameters
| id | number |
patch/v1/domains/{id}Update a domain
Any subset of the settings. Setting a tracking domain returns the CNAME to publish in the domain's `dnsRecords`; tracking only starts once it resolves.
Path parameters
| id | integer |
Body
| clickTracking | boolean | |
| openTracking | boolean | |
| trackingDomain | string | A subdomain of this domain, e.g. `links.example.com` or just `links`. Null clears it and puts tracked links back on the shared host. |
| tlsPolicy | "OPPORTUNISTIC" | "ENFORCED" | ENFORCED refuses to deliver over an unencrypted connection. OPPORTUNISTIC falls back to one. |
delete/v1/domains/{id}
Path parameters
| id | number |
Campaigns
get/v1/campaigns
Query parameters
| page | string | Page number for pagination (default: 1) |
| status | "DRAFT" | "SCHEDULED" | "RUNNING" | "PAUSED" | "SENT" | Filter campaigns by status |
| search | string | Search campaigns by name or subject |
post/v1/campaigns
Body
| name required | string | |
| from required | string | |
| subject required | string | |
| previewText | string | |
| contactBookId required | string | |
| content | string | |
| html | string | |
| replyTo | string | string[] | |
| cc | string | string[] | |
| bcc | string | string[] | |
| sendNow | boolean | |
| scheduledAt | string | Timestamp in ISO 8601 format or natural language (e.g., 'tomorrow 9am', 'next monday 10:30') |
| batchSize | integer |
get/v1/campaigns/{campaignId}
Path parameters
| campaignId required | string |
patch/v1/campaigns/{campaignId}Update a campaign
Change a campaign that has not been sent. Only a draft can be updated — a scheduled, running or paused campaign has already committed to a send time or put mail in front of part of its list.
Path parameters
| campaignId required | string |
Body
| name | string |
| from | string |
| subject | string |
| previewText | string |
| contactBookId | string |
| content | string |
| html | string |
| replyTo | string | string[] |
| cc | string | string[] |
| bcc | string | string[] |
| batchSize | integer |
delete/v1/campaigns/{campaignId}
Path parameters
| campaignId required | string |
post/v1/campaigns/{campaignId}/schedule
Path parameters
| campaignId required | string |
Body
| scheduledAt | string | Timestamp in ISO 8601 format or natural language (e.g., 'tomorrow 9am', 'next monday 10:30') |
| batchSize | integer |
post/v1/campaigns/{campaignId}/pause
Path parameters
| campaignId required | string |
post/v1/campaigns/{campaignId}/resume
Path parameters
| campaignId required | string |
Contact Books
get/v1/contactBooks/{contactBookId}/contacts
Path parameters
| contactBookId required | string |
Query parameters
| emails | string |
| page | number |
| limit | number |
| ids | string |
post/v1/contactBooks/{contactBookId}/contacts
Path parameters
| contactBookId required | string |
Body
| email required | string |
| firstName | string |
| lastName | string |
| properties | object |
| subscribed | boolean |
get/v1/contactBooks/{contactBookId}/contacts/{contactId}
Path parameters
| contactBookId required | string |
| contactId required | string |
put/v1/contactBooks/{contactBookId}/contacts/{contactId}
Path parameters
| contactBookId required | string |
Body
| email required | string |
| firstName | string |
| lastName | string |
| properties | object |
| subscribed | boolean |
patch/v1/contactBooks/{contactBookId}/contacts/{contactId}
Path parameters
| contactBookId required | string |
| contactId required | string |
Body
| firstName | string |
| lastName | string |
| properties | object |
| subscribed | boolean |
delete/v1/contactBooks/{contactBookId}/contacts/{contactId}
Path parameters
| contactBookId required | string |
| contactId required | string |
post/v1/contactBooks/{contactBookId}/contacts/bulk
Path parameters
| contactBookId required | string |
delete/v1/contactBooks/{contactBookId}/contacts/bulk
Path parameters
| contactBookId required | string |
Body
| contactIds required | string[] |
get/v1/contactBooks
post/v1/contactBooks
Body
| name required | string |
| emoji | string |
| properties | object |
| doubleOptInEnabled | boolean |
| doubleOptInFrom | string |
| doubleOptInSubject | string |
| doubleOptInContent | string |
| variables | string[] |
get/v1/contactBooks/{contactBookId}
Path parameters
| contactBookId required | string |
patch/v1/contactBooks/{contactBookId}
Path parameters
| contactBookId required | string |
Body
| name | string |
| emoji | string |
| properties | object |
| doubleOptInEnabled | boolean |
| doubleOptInFrom | string |
| doubleOptInSubject | string |
| doubleOptInContent | string |
| variables | string[] |
delete/v1/contactBooks/{contactBookId}
Path parameters
| contactBookId required | string |
Templates
get/v1/templatesList templates
Query parameters
| page | integer | 1-based page number. |
| limit | integer | Items per page, up to 100. |
| search | string |
post/v1/templatesCreate a template
Body
| name required | string | |
| subject required | string | |
| content | string | TipTap JSON. Omit to create an empty template and fill it in later. |
get/v1/templates/{templateId}Retrieve a template
Path parameters
| templateId required | string |
patch/v1/templates/{templateId}Update a template
Any subset of the fields. Supplying `content` re-renders the HTML; the HTML itself is never settable directly.
Path parameters
| templateId required | string |
Body
| name | string |
| subject | string |
| content | string |
delete/v1/templates/{templateId}Delete a template
Path parameters
| templateId required | string |
post/v1/templates/{templateId}/duplicateDuplicate a template
Path parameters
| templateId required | string |
Segments
get/v1/segmentsList segments
post/v1/segmentsCreate a segment
Body
| name required | string |
| contactBookId required | string |
| filters required | object |
get/v1/segments/{segmentId}Retrieve a segment
Path parameters
| segmentId required | string |
patch/v1/segments/{segmentId}Update a segment
Path parameters
| segmentId required | string |
Body
| name | string |
| filters | object |
delete/v1/segments/{segmentId}Delete a segment
The contacts it matched are untouched — a segment is only a filter.
Path parameters
| segmentId required | string |
get/v1/segments/{segmentId}/contactsList the contacts a segment matches
Evaluated when you ask, not stored — a segment has no membership of its own.
Path parameters
| segmentId required | string |
Query parameters
| page | integer | 1-based page number. |
| limit | integer | Items per page, up to 100. |
Suppressions
get/v1/suppressionsList suppressions
Query parameters
| page | integer | 1-based page number. |
| limit | integer | Items per page, up to 100. |
| search | string | |
| reason | "HARD_BOUNCE" | "COMPLAINT" | "MANUAL" |
post/v1/suppressionsSuppress an address
Body
| email required | string | |
| reason | "HARD_BOUNCE" | "COMPLAINT" | "MANUAL" | HARD_BOUNCE and COMPLAINT are recorded automatically; MANUAL is what you add yourself. |
| source | string |
get/v1/suppressions/{email}Check whether an address is suppressed
404 when the address is not on the list, so this doubles as a pre-send check.
Path parameters
| email required | string |
delete/v1/suppressions/{email}Remove an address from the suppression list
Also clears the address from the sending provider's own suppression list where one applies. Removing a hard bounce means mail will be attempted again — do it only when you know the address is good.
Path parameters
| email required | string |
post/v1/suppressions/bulkSuppress many addresses
For importing a list from another provider. Duplicates within the request and addresses already suppressed are both fine.
Body
| emails required | string[] | |
| reason | "HARD_BOUNCE" | "COMPLAINT" | "MANUAL" | HARD_BOUNCE and COMPLAINT are recorded automatically; MANUAL is what you add yourself. |
post/v1/suppressions/bulk/removeRemove many addresses from the suppression list
POST rather than DELETE: a body on DELETE is permitted by the spec and dropped by enough proxies and HTTP clients to be unreliable.
Body
| emails required | string[] |
Webhooks
get/v1/webhooksList webhook endpoints
post/v1/webhooksCreate a webhook endpoint
The response includes the signing secret. It is shown once and cannot be retrieved again — store it now.
Body
| url required | string | |
| eventTypes required | "contact.created" | "contact.updated" | "contact.deleted" | "domain.created" | "domain.verified" | "domain.updated" | "domain.deleted" | "email.queued" | "email.sent" | "email.delivery_delayed" | "email.delivered" | "email.bounced" | "email.rejected" | "email.rendering_failure" | "email.complained" | "email.failed" | "email.cancelled" | "email.suppressed" | "email.opened" | "email.clicked" | "webhook.test"[] | |
| description | string | |
| domainIds | integer[] | Restrict deliveries to these domains. Omit to receive events for all of them. |
get/v1/webhooks/{webhookId}Retrieve a webhook endpoint
Path parameters
| webhookId required | string |
patch/v1/webhooks/{webhookId}Update a webhook endpoint
Path parameters
| webhookId required | string |
Body
| url | string | |
| eventTypes | "contact.created" | "contact.updated" | "contact.deleted" | "domain.created" | "domain.verified" | "domain.updated" | "domain.deleted" | "email.queued" | "email.sent" | "email.delivery_delayed" | "email.delivered" | "email.bounced" | "email.rejected" | "email.rendering_failure" | "email.complained" | "email.failed" | "email.cancelled" | "email.suppressed" | "email.opened" | "email.clicked" | "webhook.test"[] | |
| description | string | |
| domainIds | integer[] | |
| rotateSecret | boolean | Issue a new signing secret. The new one is returned; the old one stops working immediately. |
delete/v1/webhooks/{webhookId}Delete a webhook endpoint
Path parameters
| webhookId required | string |
get/v1/webhooks/{webhookId}/attemptsList delivery attempts
What was sent, what came back, and how many times it was retried. A delivery is attempted six times with exponential backoff before it is given up on.
Path parameters
| webhookId required | string |
Query parameters
| limit | integer | |
| cursor | string | The `nextCursor` from the previous page. |
Api Keys
get/v1/apiKeysList API keys
post/v1/apiKeysCreate an API key
The full key is in the response and is not recoverable afterwards. Store it before you close the connection.
Body
| name required | string |
| permission | "FULL" | "SENDING" |
| domainId | integer |
delete/v1/apiKeys/{apiKeyId}Delete an API key
Takes effect immediately. Any request still using it starts failing.
Path parameters
| apiKeyId | integer |
Analytics
get/v1/analytics/email-time-series
Query parameters
| days | "7" | "30" | Number of days to retrieve data for (default: 30) |
| domainId | string | Filter by domain ID |
get/v1/analytics/reputation-metrics
Query parameters
| domainId | string | Filter by domain ID |