API reference
Every endpoint, every operation, and the conventions that hold across all of them.
Base URL https://emails.sh/v1. Every request carries Authorization: Bearer esh_..., every body is JSON, and every response is JSON. There is no versioning header: /v1 is the version, and a breaking change would be /v2.
| Convention | What it means |
|---|---|
Content-Type | application/json on anything with a body. A form encoding is rejected. |
Timestamps | RFC 3339 with a Z offset, always UTC. |
Ids | Prefixed and opaque: em_ for an email, dom_ for a domain, whd_ for a webhook delivery. Do not parse them. |
Errors | Two shapes. Most routes answer { error: { code, message, next } }; topics, suppressions, templates, the contacts collection, and the rate limiter answer { error: "code", message?, hint? }. Parse both. See /docs/errors. |
Rate limits | 600 requests a minute per key, which is 10 a second, and 30 a minute per IP with no key. Over it, 429 with retry-after in seconds. |
Pagination | limit and before on list endpoints. Newest first unless the endpoint says otherwise. |
Emails
/v1/emailsSend one email.
/v1/emails/batchSend up to 100 in one request.
/v1/emails/:idStatus and delivery events for one email.
/v1/emails/:id{ send_at } moves a booked send to a new time. It keeps its id. 409 too_late_to_reschedule once it has gone.
/v1/emailsThe delivery log. limit defaults to 25 and tops out at 100.
/v1/emails/:id/cancelCall off an email booked with send_at, before it goes.
/v1/messages/scheduled/:idThe same cancel, by the older spelling. Still works.
emails.send
Send one email. The endpoint every integration starts with, and the only one many ever use. 200 when the status is queued, 202 when it is scheduled.
Arguments
fromstringrequired- Sender, as an address or as "Name <address>". The domain must be verified on this workspace, or be onboarding@emails.sh while you are testing.
tostring[]required- Recipients. Up to 50 addresses counted across to, cc, and bcc together.
subjectstringrequiredhtmlstring- HTML body. Give html, text, or both.
textstring- Plain-text body. Sent as the alternative part when html is present.
ccstring[]bccstring[]reply_tostring | string[]- Where replies go, if not the from address.
headersRecord<string, string>- Extra headers, for example List-Unsubscribe.
attachments{ filename, content_base64, content_type? }[]- Base64 bytes with no data: prefix. 40 MB total per message, counted after base64 encoding.
tagsRecord<string, string>- Labels stored with the email and echoed on every webhook for it.
send_atstring- When to send it, up to 30 days out. An ISO 8601 timestamp such as 2026-08-04T09:00:00Z, a relative offset such as "in 1 min", or a clock time such as "tomorrow at 9am", read as UTC. Omit to send now.
idempotency_keystring- Your own id for this send. A repeat within 24 hours returns the first result instead of sending again.
Returns{ id, status: "queued" | "scheduled" }
emails.batch
Send up to 100 emails in one request. Each entry succeeds or fails on its own, and the response keeps the order you sent them in.
Arguments
emailsSend[]required- Up to 100 send bodies, each exactly as emails.send takes one.
Returns{ data: ({ id, status } | { id: null, status: "failed", error })[] }
emails.get
Status and delivery events for one email: when it was accepted, when the receiving server took it, and the bounce or complaint if there was one.
Arguments
idstringrequired- The id a send returned.
Returns{ id, status, to, subject, created_at, events[] }
Domains
/v1/domainsDomains, with the records a pending one still needs.
/v1/domains{ domain } returns every DNS record to publish.
/v1/domains/:idOne domain, with the records it still needs if it is pending.
/v1/domains/:id/verifyCheck the records now and report which are missing.
/v1/domains/:id{ tracking_host } sets the hostname in front of tracked links. null goes back to the shared one.
/v1/domains/:idRemove a domain. DELETE /v1/domains?id= is the older spelling and still works.
domains.list
Every sending domain on the workspace, with the DNS records a pending one still needs.
Takes no arguments.
Returns{ domains: Domain[] }
domains.create
Add a sending domain. The response carries every DNS record to publish, so setup can finish without opening the dashboard.
Arguments
domainstringrequired- A domain or subdomain you control, for example mail.acme.com.
Returns{ id, domain, verification_status, records[] }
domains.get
One domain, in the shape the list gives it, with the DNS records still to publish if it is pending. An id from another workspace reads as missing.
Arguments
idstringrequired- The id the create response returned.
ReturnsDomain
domains.verify
Check the records now rather than waiting for the nightly pass. Safe to call repeatedly, and the answer says which records are still missing.
Arguments
idstringrequired
Returns{ domain, verified, records: (Record & { found })[] }
domains.delete
Remove a domain. Anything still sending from it starts failing, so move senders first.
Arguments
idstringrequired
Returns{ deleted: id }
API keys
/v1/api-keysKeys on the workspace. Values are never listed.
/v1/api-keys{ name, scopes? } returns the key once.
/v1/api-keys/:idRevoke a key, effective on the next request.
api_keys.list
Keys on the workspace, with the last time each was used. Values are never listed.
Takes no arguments.
Returns{ api_keys: ApiKey[] }
api_keys.create
Create a key. The value is returned once and never again.
Arguments
namestringrequired- What it is for, so a later reader can revoke the right one.
scopesstring[]- Defaults to full access. Give ["mail:send"] to a key that only sends.
Returns{ id, name, key }
api_keys.revoke
Revoke a key. It stops working on the next request, with no grace period.
Arguments
idstringrequired
Returns{ deleted: id }
Webhooks
/v1/webhooksEndpoints. Secrets are not listed.
/v1/webhooks{ url, events?, headers? } returns the signing secret once.
/v1/webhooks/:idOne endpoint. The secret is never in a read.
/v1/webhooks/:idChange an endpoint. rotate_secret returns a new secret once. PATCH /v1/webhooks?id= is the older spelling and still works.
/v1/webhooks/:idRemove an endpoint. DELETE /v1/webhooks?id= is the older spelling and still works.
/v1/webhooks/deliveriesWhat each attempt got back. webhook_id and limit narrow it.
/v1/webhooks/deliveries{ webhook_id } sends a test; { delivery_id } replays a stored one.
webhooks.list
Registered endpoints and the events each is subscribed to. Signing secrets are not listed.
Takes no arguments.
Returns{ webhooks: Webhook[] }
webhooks.create
Register an endpoint. The signing secret comes back once, in this response.
Arguments
urlstringrequired- An https endpoint of yours.
eventsstring[]- Defaults to email.delivered, email.bounced, and email.complained.
headersRecord<string, string>- Sent with every delivery, for a gateway that wants its own token.
Returns{ id, url, events, secret }
webhooks.get
One endpoint, with its events, whether it is active, and its failure counters. The signing secret is not in a read, at any scope.
Arguments
idstringrequired
ReturnsWebhook
webhooks.update
Change an endpoint in place. Fields you leave out are left alone.
Arguments
idstringrequiredurlstring- A new https endpoint. Fixing a typo here keeps the id and the secret.
eventsstring[]- Replaces the subscription wholesale rather than adding to it.
activeboolean- Turning it back on clears the failure count, so it does not trip on the next miss.
headersRecord<string, string>rotate_secretboolean- Mints a new signing secret and returns it once. The old one keeps signing for a grace window, so the receiver can be redeployed without dropping events.
ReturnsWebhook, plus secret and previous_secret_valid_until when rotate_secret was set
webhooks.deliveries
What each attempt got back: status code, response body, and duration. This is how you tell a broken endpoint from a missing event.
Arguments
webhook_idstringlimitnumber- Defaults to 25.
Returns{ deliveries: Delivery[] }
webhooks.delete
Remove an endpoint. Queued deliveries for it are dropped.
Arguments
idstringrequired
Returns{ deleted: id }
Audiences
/v1/audiencesAudiences on the workspace.
/v1/audiences{ name, description? } creates one.
/v1/audiences/:idOne audience, with its double opt-in setting and where that setting came from.
/v1/audiences/:id{ name?, description?, require_double_opt_in?, confirmation_subject?, confirmation_body? }
/v1/audiences/:idSoft delete an audience.
/v1/audiences/:id/contactsMembers. ?status=&limit=&offset=. id on each row is the membership id.
/v1/audiences/:id/contactsBulk import of { email, attributes?, status?, tags? } rows, as JSON or CSV. ?dry_run=true reports without writing.
/v1/audiences/:id/contacts/:memberOne membership, by membership id.
/v1/audiences/:id/contacts/:member{ attributes?, subscribed?, status? } on one membership.
/v1/audiences/:id/contacts/:memberRemove one membership. It records no unsubscribe.
/v1/audiences/:id/contacts/:member/confirmSend the double opt-in confirmation email. Nothing else ever sends it.
audiences.list
Audiences on the workspace. An audience is a named list of contacts with their subscription state.
Takes no arguments.
Returns{ audiences: Audience[] }
audiences.create
Create an audience.
Arguments
namestringrequired
Returns{ id, name }
audiences.members
Members of one audience. Each row carries a membership id, which is the id every other member route takes, and a contact_id, which is the workspace-level contact behind it.
Arguments
audience_idstringrequiredstatusstring- subscribed, unsubscribed, pending, or cleaned.
limitnumber- Defaults to 100, maximum 1000.
offsetnumber
Returns{ contact_count, subscribed_count, contacts: Member[] }
audiences.import
Bulk import into an audience. Up to 10000 rows and 8 MB per call. An address already on the suppression list lands as cleaned rather than subscribed and is counted in held_back.
Arguments
audience_idstringrequiredcontacts{ email, attributes?, status?, tags? }[]required- Rows to import. A bare array, { contacts: [] }, { data: [] }, or one object all work, and CSV works when the Content-Type says csv.
dry_runboolean- Query parameter. Reports what would happen and writes nothing.
Returns{ created, updated, unchanged, duplicates, tags_added, imported, held_back, skipped, held[], errors[] }
audiences.updateMember
Change one membership. Recording an unsubscribe here is what keeps the address from being mailed by the next broadcast.
Arguments
audience_idstringrequiredmemberstringrequired- The membership id from audiences.members. Not a contact id and not an email address.
attributesRecord<string, string>- Per-list merge fields for this membership.
subscribedboolean- Wins over status when both are given.
statusstring- subscribed, unsubscribed, pending, or cleaned.
ReturnsMember
audiences.removeMember
Remove one membership from an audience. It does not delete the contact, and it does not record an unsubscribe: set status to unsubscribed for that.
Arguments
audience_idstringrequiredmemberstringrequired- The membership id.
Returns{ deleted: id }
Broadcasts
/v1/broadcastsBroadcasts, newest first. limit defaults to 50 and tops out at 100.
/v1/broadcastsCreate a draft. from is the one required field.
/v1/broadcasts/:idOne broadcast with its body, its stats, and its problems[].
/v1/broadcasts/:idEdit a draft. from is not patchable, and a broadcast past draft answers 409.
/v1/broadcasts/:idCancel it.
/v1/broadcasts/:id/cancelThe same cancel, as a POST.
/v1/broadcasts/:id/send{ scheduled_at? }. Without it, it goes now.
/v1/broadcasts/:id/test{ to } sends it to up to 5 addresses of yours.
/v1/broadcasts/:id/preview?email= renders it without sending, and lists the merge fields.
/v1/broadcasts/previewThe same render for a body you have not saved. Nothing is written.
/v1/broadcasts/:id/recipients?status=&limit=&offset= over per-recipient results.
broadcasts.create
Create a broadcast as a draft. The response carries ready and problems[], so you can tell whether it can send yet without trying.
Arguments
fromstringrequired- Sender, as an address or as "Name <address>". The domain must be verified on this workspace, or be onboarding@emails.sh while you are testing.
audience_idstring- Who it goes to. Required before it can send.
segment_idstring- Narrow the audience to the members a segment matches.
topic_idstring- The topic the send is filed under, which is what gives it a working one-click opt-out.
subjectstringnamestring- What it is called in the dashboard. Not seen by a recipient.
htmlstringtextstringtemplate_idstring- Use a stored template instead of an inline body.
template_version_idstringreply_tostringtrack_opensbooleantrack_clicksboolean
Returns{ id, status: "draft", ready, problems[] }
broadcasts.send
Send a draft, or book it. Sending now answers with the recipient count and how many were skipped; booking answers with the time it will go.
Arguments
idstringrequiredscheduled_atstring- RFC 3339, strictly in the future and at most 30 days out. Omit to send now.
Returns{ id, status, queued, batches, skipped, recipients } or { id, status, scheduled_at }
broadcasts.test
Send the broadcast to yourself first, rendered exactly as a recipient would get it. It does not change the draft status.
Arguments
idstringrequiredtostring | string[]required- Up to 5 addresses.
Returns{ sent, skipped }
broadcasts.preview
The rendered subject and body without sending anything, plus merge_fields saying which fields the audience actually supplies and which are missing.
Arguments
idstringrequiredemailstring- Render for one member of the audience rather than for a sample.
Returns{ subject, html, text, merge_fields[], audience_sample_size }
broadcasts.previewContent
The same render as broadcasts.preview, for a body you have not saved. Nothing is written and nothing is sent, so id comes back null.
Arguments
subjectstringrequiredhtmlstring- One of html or text is required.
textstringfromstring- Shown back on the preview. It is not resolved against your domains here.
replyTostringaudienceIdstring- Render against a real member of this audience. Leave it out and the recipient is a placeholder with no attributes.
emailstring- Which member of audienceId to render for.
Returns{ id: null, subject, html, text, headers, merge_fields[], audience_sample_size }
broadcasts.recipients
Per-recipient results for one broadcast, with the reason a skipped or failed row did not go.
Arguments
idstringrequiredstatusstring- pending, sent, delivered, bounced, complained, failed, or skipped.
limitnumber- Defaults to 100, maximum 500.
offsetnumber
Returns{ stats, recipients: Recipient[] }
broadcasts.cancel
Call off a scheduled or sending broadcast. Messages already handed to the mail servers have gone.
Arguments
idstringrequired
Returns{ id, status: "cancelled" }
Segments
/v1/segmentsSegments. ?audience_id= narrows to one audience.
/v1/segments{ name, audience_id?, match?, rules? } creates one.
/v1/segments/:id?count=live recomputes member_count instead of reading the cached one.
/v1/segments/:idRules are replaced wholesale, never merged.
/v1/segments/:idRemove a segment. Contacts are untouched.
/v1/segments/:id/members?mailable=true&limit=&after= over who it matches now.
segments.create
A saved filter over contacts. Membership is computed when it is read rather than stored, so a segment is never stale.
Arguments
namestringrequiredaudience_idstring- Leave it out for a workspace-wide segment. status and joined rules need one.
match"all" | "any"- Defaults to all.
rulesRule[]- Up to 20, and never nested.
descriptionstring
Returns{ id, name, describes, member_count }
segments.get
One segment, its rules, and the sentence describing them.
Arguments
idstringrequiredcountstring- Pass live to recompute member_count now instead of reading the cached one.
ReturnsSegment
segments.update
Change a segment. Send the whole rule list every time, including the rules you are keeping.
Arguments
idstringrequiredmatch"all" | "any"rulesRule[]- Replaced wholesale. Rules are never merged into what is there.
ReturnsSegment
segments.members
Who a segment currently matches, as a cursor-paged list.
Arguments
idstringrequiredmailableboolean- Only members who are subscribed and not suppressed.
limitnumber- Defaults to 100, maximum 1000.
afterstring- The next_after cursor from the previous page.
Returns{ segment_id, total, next_after, members[] }
Contacts
/v1/contacts?q=&lookup=&limit=. Accept: text/vcard returns a .vcf instead of JSON.
/v1/contactsField mode, or { vcard } for up to 1000 cards at once.
/v1/contacts/:idOne contact.
/v1/contacts/:idChange a contact.
/v1/contacts/:idRemove a contact.
/v1/contacts/duplicatesLikely duplicate pairs. POST merges { survivor_id, loser_id }.
/v1/contacts/:id/attributesWorkspace-level attributes on a contact.
/v1/contacts/:id/attributesMerge patch. null clears one name.
/v1/contacts/:id/subscriptionsEvery address, whether it is suppressed, and what it is subscribed to.
contacts.attributes
Workspace-level facts about a contact, readable by every segment and automation. These are not the per-list merge fields a broadcast substitutes.
Arguments
idstringrequiredattributesRecord<string, string | null>- A merge patch: names you send are written, names you leave out are untouched, and null clears one. 1 to 100 names per call.
Returns{ attributes, changed[] }
contacts.subscriptions
Every address on a contact, whether it is suppressed, and the audiences and topics it is subscribed to. The one call that answers "will this person receive anything".
Arguments
idstringrequired
Returns{ contact_id, subscriptions[] }
Templates
/v1/templatesTemplates on the workspace.
/v1/templates{ name, slug?, subject?, html?, text?, variables?, publish? }
/v1/templates/:idOne template with every version.
/v1/templates/:id{ name?, slug?, description? } only. Content is never edited here.
/v1/templates/:idSoft delete.
/v1/templates/:id/versionsVersions and which one is published.
/v1/templates/:id/versionsWrite a draft version. It never publishes.
/v1/templates/:id/publish{ version_id } or { version }, or neither to publish the latest.
/v1/templates/:id/renderStrict render of the published version, exactly as a send does it.
/v1/templates/:id/previewLenient render of any version, with markup warnings.
templates.create
Create a template and its first version.
Arguments
namestringrequiredslugstring- What your code names. Derived from the name when you leave it out.
subjectstringhtmlstringtextstringvariables(string | { name, default?, required? })[]publishboolean- Defaults to false, so a new template starts with nothing live.
Returns{ id, name, slug, published_version_id, latest_version, sendable }
templates.addVersion
Write a new draft version. It never publishes, so editing a password reset reaches nobody until you say so.
Arguments
idstringrequiredsubjectstring- Defaults to an empty string.
htmlstring- One of html or text is required.
textstringvariables(string | { name, default?, required? })[]
ReturnsVersion
templates.publish
Point live sends at a version.
Arguments
idstringrequiredversion_idstring- Or version, as a number. Omit both to publish the latest.
Returns{ published_version_id }
templates.render
Render the published version strictly, exactly as a send would. A missing variable is 422 template_variables_missing rather than a blank.
Arguments
idstringrequiredvariablesRecord<string, string>preheaderstring
Returns{ subject, html, text, preheader }
templates.preview
Render leniently, filling anything you did not supply with a sample. Also returns warnings about markup mail clients will not render.
Arguments
idstringrequiredversion_idstring- Preview a draft rather than the published version.
variablesRecord<string, string>preheaderstring
Returns{ subject, html, text, values, filled_with_samples, warnings[] }
Topics
/v1/topics?include_archived=true to see the retired ones as well.
/v1/topics{ name, key?, description?, default_opt_in?, required? }
/v1/topics/:idOne topic.
/v1/topics/:idEverything but key, which is immutable.
/v1/topics/:idArchives rather than deletes.
/v1/topics/preferences?email= returns what one address has said, and its preference page URL.
/v1/topics/preferences{ email, topic, subscribed?, source? }. Omit subscribed to ask rather than write.
topics.create
A named category of mail a recipient can turn off on its own.
Arguments
namestringrequiredkeystring- Lowercase letters, digits, and hyphens, at least 2 characters. Derived from the name when you leave it out, and never changeable afterwards.
descriptionstringdefault_opt_inboolean- Defaults to true.
requiredboolean- Defaults to false. A required topic cannot be switched off.
ReturnsTopic
topics.preferences
Read or record what one address has said about your topics. Keyed by address, so somebody who was never in an audience still has a working opt-out.
Arguments
emailstringrequiredtopicstring- A key or an id. On the POST.
subscribedboolean- Leave it out to ask rather than to write.
sourcestring- Where the answer came from, for your own audit trail.
Returns{ email, preference_url, preferences[] }
Automations
/v1/automationsAutomations, with their trigger, version, and last error.
/v1/automationsRaw YAML, or { yaml } as JSON.
/v1/automations/:idOne automation, with its graph, its YAML, and its trigger URL if it has one.
/v1/automations/:id{ enabled?, yaml? }
/v1/automations/:idRemove it, and cancel every waiting run.
/v1/automations/:id.yamlThe document, with the version in x-emailssh-automation-version.
/v1/automations/:id.yamlReplace the document with the raw YAML body.
/v1/automations/:id/versionsThe last 50 versions, each with its YAML.
/v1/automations/:id/versions{ version_id } restores one as a new version.
/v1/automations/:id/trigger{ email | contact_id, idempotency_key, data? } starts a run.
/v1/automations/:id/runs?status=&limit= over runs.
/v1/automations/:id/runs/:runIdOne run and every step it executed.
/v1/automations/:id/runs/:runIdCancel a run that is waiting.
automations.create
Create an automation from a YAML document. It is validated whole: a refusal names the field, says what to write instead, and carries the line number.
Arguments
yamlstringrequired- The document. Post it raw with a YAML or text/plain Content-Type, or wrapped as { yaml }.
Returns{ id, name, slug, trigger, enabled, version, yaml }
automations.pull
GET /v1/automations/:id.yaml. The raw document, with the current version in the x-emailssh-automation-version response header. Comments and key order survive the round trip.
Arguments
idstringrequired
Returnstext/yaml
automations.push
PUT /v1/automations/:id.yaml. Replaces the document and writes a new version.
Arguments
idstringrequiredyamlstringrequired- The raw body. This is the write half of the pull and push pair a CI job uses.
Returnstext/yaml
automations.trigger
Start a run of an automation whose trigger is api.call.
Arguments
idstringrequiredemailstring- One of email or contact_id is required.
contact_idstringidempotency_keystringrequired- Required, not optional. A repeat answers 409 duplicate.
dataobject- Anything here is readable in the flow as {{ trigger.<name> }}.
Returns{ run_id, status }
automations.runs
Runs of one automation, with how many steps executed, how many emails went, and the error if it stopped.
Arguments
idstringrequiredstatusstring- running, waiting, completed, stopped, failed, or cancelled.
limitnumber- Defaults to 50, maximum 200.
Returns{ runs: Run[] }
Analytics
/v1/analytics?from=&to=&group_by=&breakdown=&mail_class=&domain=&template_id=&tag=
analytics.get
Sends, deliveries, bounces, complaints, clicks, and opens over a window, as totals and as a series.
Arguments
fromstring- YYYY-MM-DD in UTC. Defaults to 30 days ago.
tostring- YYYY-MM-DD in UTC, inclusive. The window may be at most 400 days.
group_by"day" | "week" | "month"- Defaults to day.
breakdown"domain" | "tag" | "mail_class" | "template"tag_keystring- Required when breakdown is tag.
mail_class"transactional" | "marketing"domainstringtemplate_idstringtagstring- Filter to one tag, written key:value.
Returns{ range, totals, series[], breakdown, notes }
Suppressions
/v1/suppressions?reason=&email=&limit=&before= over blocked addresses.
/v1/suppressions{ email, reason? } blocks one yourself.
/v1/suppressions/:idLift one by id. A global row answers 404, and so does another workspace's.
/v1/suppressions?email= clears one by address. ?id= is the older spelling of the route above and still works.
suppressions.list
Addresses nothing will reach on this workspace, and why each one is there. Rows with is_global set are ours rather than yours.
Arguments
reason"bounce" | "complaint" | "unsub" | "manual"emailstring- Ask about one address.
limitnumber- Defaults to 50, from 1 to 200.
beforestring- The next_cursor from the previous page.
Returns{ suppressions[], next_cursor, total }
suppressions.create
Block an address yourself, for somebody who asked you to stop by replying rather than by clicking.
Arguments
emailstringrequiredreasonstring- Defaults to manual.
ReturnsSuppression
suppressions.delete
Clear one, when you know the address is good again. A global row answers 404 and cannot be cleared.
Arguments
idstring- Give id or email.
emailstring
Returns{ deleted }
Dedicated IPs
/v1/ipsDedicated addresses, shared pools, the default region, and data residency.
/v1/ips/:idOne address, with a live reverse DNS check.
/v1/ips/:id{ paused?, daily_cap? } and nothing else.
ips.list
Dedicated addresses on the workspace, the shared pools anything else goes through, the default region, and where sending, storage, and compute physically happen.
Takes no arguments.
Returns{ default_region, residency, ips[], shared_pools[] }
ips.get
One address, plus a live reverse DNS check: what the PTR should say, what it says, and whether the forward lookup confirms it.
Arguments
idstringrequired
ReturnsIp & { reverse_dns }
ips.update
The only two things about an address you can change. POST /v1/ips takes one out of inventory and DELETE /v1/ips/:id hands it back; this route is for pausing one and lowering its cap.
Arguments
idstringrequiredpausedboolean- Stop routing mail through it without giving it up.
daily_capnumber | null- A non-negative integer, or null for no cap of your own. It can lower the warmup allowance and never raise it.
ReturnsIp
Received mail
/v1/messages?unread_only=true&thread_id=&limit= over received mail.
/v1/messages/:idOne received message in full.
/v1/messages/:id/reply-all{ body } answers on the same thread.
/v1/messages/:id/forward{ to, body?, mode? }
/v1/messages/:id/archive{ archived?, unread? }
/v1/messages/:id/attachments/:filenameOne attachment, as its own bytes.
/v1/threadsConversations, newest first.
/v1/threads/:idEvery message in one conversation.
/v1/search?q= ranked full-text search over received mail.
messages.list
Mail that arrived at an address on a domain of yours with inbound turned on.
Arguments
unread_onlybooleanthread_idstringlimitnumber- Defaults to 25.
Returns{ messages: Message[] }
messages.get
One received message with its full body, headers, and attachment list.
Arguments
idstringrequired
ReturnsMessage
messages.reply
Answer a received message on its own thread, with References and In-Reply-To set for you.
Arguments
idstringrequired- The message you are answering.
htmlstringtextstring
Returns{ id, status }
threads.get
Every message in one conversation, oldest first.
Arguments
idstringrequired
ReturnsThread