Sending email from your own domains, over SMTP or through Send an email. Turn email on for a domain in the project, publish the DKIM, SPF and DMARC records it returns, and verify them; then create an SMTP credential for each sender address. [SMTP Setup](/docs/guides/smtp-setup) walks through it and has the server settings. Send an email takes its content inline or from a stored template; see [Email templates](/docs/api/email-templates) and the [Email Templates guide](/docs/guides/email-templates). Every email sent is billed on the project's subscription, each recipient counting as one email, and there is no free allowance. Projects start on pay as you go, with no monthly fee; a plan includes a number of emails each month for a flat price and bills the emails beyond it at a lower rate. Change the email plan moves between them. See [Email pricing](/docs/pricing/email). Sending is held under three ceilings, all shown by Get send limits. The **monthly cap** is your budget — the most the project will send, and pay for, in one quota month — and you set it. The **hourly** and **daily limits** are burst guards that stop a runaway loop from spending the month in an afternoon; they are raised on request, after review. Reaching any of them pauses sending. An hourly or daily pause lifts on its own as the window moves on; a monthly one lifts when the next quota month begins, or as soon as the cap is raised above what has already been sent. The project's biller is emailed whenever sending is paused. Adding a domain whose DNS is hosted elsewhere for email, and turning email off for a domain, are done in the control panel for now.
Every domain email is turned on for in the project, oldest first, with its SMTP credentials (never their passwords) and the DNS records it needs.
Auth: a project API key carrying email:read.
Authorization
apiKey A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.
In: header
Path Parameters
1 <= lengthResponse Body
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/projects/string/email"{ "success": true, "data": [ { "id": "string", "organizationId": "string", "domainId": "string", "status": "DNS_PENDING", "dkimSelector": "string", "dkimPublicKey": "string", "verifiedAt": "2019-08-24T14:15:22Z", "createdAt": "2019-08-24T14:15:22Z", "updatedAt": "2019-08-24T14:15:22Z", "domain": { "id": "string", "name": "string", "status": "PENDING", "type": "PURCHASED" }, "credentials": [ { "id": "string", "label": "string", "fromAddress": "string", "smtpUsername": "string", "sentCount": -9007199254740991, "lastUsedAt": "2019-08-24T14:15:22Z", "createdAt": "2019-08-24T14:15:22Z" } ], "dnsRecords": [ { "type": "string", "name": "string", "value": "string", "purpose": "DKIM", "description": "string" } ], "sending": { "identity": "VERIFIED", "billingRequired": true } } ]}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}Turns on email sending for a domain already in the project, identified by its domainId.
A DKIM key is generated for it, and the response carries the three records to publish in
the domain's DNS: DKIM, SPF and DMARC. Publish them, then call Verify email DNS records.
Nothing is billed until the domain is verified.
Members with the viewer role are refused, as is a project blocked over an unpaid invoice (403 ACCOUNT_BLOCKED).
Auth: a project API key carrying all of email:read and email:write.
Rate limit: this endpoint is rate limited; a 429 means back off and retry.
Authorization
apiKey A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.
In: header
Path Parameters
1 <= lengthRequest Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/projects/string/email" \ -H "Content-Type: application/json" \ -d '{ "domainId": "string" }'{ "success": true, "data": { "id": "string", "organizationId": "string", "domainId": "string", "status": "DNS_PENDING", "dkimSelector": "string", "dkimPublicKey": "string", "verifiedAt": "2019-08-24T14:15:22Z", "createdAt": "2019-08-24T14:15:22Z", "updatedAt": "2019-08-24T14:15:22Z", "domain": { "id": "string", "name": "string", "status": "PENDING", "type": "PURCHASED" }, "credentials": [ { "id": "string", "label": "string", "fromAddress": "string", "smtpUsername": "string", "sentCount": -9007199254740991, "lastUsedAt": "2019-08-24T14:15:22Z", "createdAt": "2019-08-24T14:15:22Z" } ], "dnsRecords": [ { "type": "string", "name": "string", "value": "string", "purpose": "DKIM", "description": "string" } ], "sending": { "identity": "VERIFIED", "billingRequired": true } }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}One email domain, in the same shape as an entry of List email domains, plus sending: whether
mail can actually be sent as it, and what to do when it cannot.
Auth: a project API key carrying email:read.
Authorization
apiKey A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.
In: header
Path Parameters
1 <= length1 <= lengthResponse Body
application/json
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/projects/string/email/string"{ "success": true, "data": { "id": "string", "organizationId": "string", "domainId": "string", "status": "DNS_PENDING", "dkimSelector": "string", "dkimPublicKey": "string", "verifiedAt": "2019-08-24T14:15:22Z", "createdAt": "2019-08-24T14:15:22Z", "updatedAt": "2019-08-24T14:15:22Z", "domain": { "id": "string", "name": "string", "status": "PENDING", "type": "PURCHASED" }, "credentials": [ { "id": "string", "label": "string", "fromAddress": "string", "smtpUsername": "string", "sentCount": -9007199254740991, "lastUsedAt": "2019-08-24T14:15:22Z", "createdAt": "2019-08-24T14:15:22Z" } ], "dnsRecords": [ { "type": "string", "name": "string", "value": "string", "purpose": "DKIM", "description": "string" } ], "sending": { "identity": "VERIFIED", "billingRequired": true } }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}Creates a username and password for sending over SMTP as one address. fromAddress must be
on the email domain or one of its subdomains, and is the only sender the credential can use:
the relay sets every message's From to it. Create one credential per sender address.
The password is returned once, in this response, and cannot be read back — store it before discarding the response. See SMTP Setup for the server to connect to.
Members with the viewer role are refused, as is a project blocked over an unpaid invoice (403 ACCOUNT_BLOCKED).
Auth: a project API key carrying all of email:read and email:write.
Rate limit: this endpoint is rate limited; a 429 means back off and retry.
Authorization
apiKey A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.
In: header
Path Parameters
1 <= length1 <= lengthRequest Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/projects/string/email/string/credentials" \ -H "Content-Type: application/json" \ -d '{ "label": "string", "fromAddress": "[email protected]" }'{ "success": true, "data": { "id": "string", "label": "string", "fromAddress": "string", "smtpUsername": "string", "sentCount": -9007199254740991, "createdAt": "2019-08-24T14:15:22Z", "smtpPassword": "string" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}Deletes the credential, so it can no longer authenticate.
Members with the viewer role are refused.
Auth: a project API key carrying all of email:read and email:write.
Authorization
apiKey A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.
In: header
Path Parameters
1 <= length1 <= length1 <= lengthResponse Body
application/json
application/json
application/json
application/json
application/json
curl -X DELETE "https://example.com/v1/projects/string/email/string/credentials/string"{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}Looks the domain's DKIM, SPF and DMARC records up in public DNS. When all three are found
the domain becomes ACTIVE. When any is missing the domain stays DNS_PENDING and the
answer is still 200: each record says whether it was found. DNS changes take a while to
propagate, so retrying is normal. On an ACTIVE domain it checks again and repairs what
Get an email domain reports under sending. Checks are rate-limited per project; a 429
means wait a few minutes and retry.
Every email is billed, so the project's email plan has to be on its subscription before the
domain goes live — one is started if the project has none. A project that cannot be billed
is refused with 402 and the domain stays DNS_PENDING — see
Billing for the codes.
Finding these records under a domain also proves you control its DNS, so an external domain
still waiting for its ownership check becomes ACTIVE in the project at the same time.
Members with the viewer role are refused.
Auth: a project API key carrying all of email:read and email:write.
Authorization
apiKey A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.
In: header
Path Parameters
1 <= length1 <= lengthResponse Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/projects/string/email/string/verify"{ "success": true, "data": { "status": "DNS_PENDING", "verifiedAt": "2019-08-24T14:15:22Z", "records": [ { "type": "string", "name": "string", "value": "string", "purpose": "DKIM", "description": "string", "verified": true, "error": "string" } ] }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}Asks for a higher hourly limit, with an optional reason. The request is reviewed by the
Cosmoner team; if it is approved, the daily limit is raised alongside it. Only one request can
wait for review at a time — Get send limits shows it as pendingRequest.
Members with the viewer role are refused.
Auth: a project API key carrying all of email:read and email:write.
Authorization
apiKey A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.
In: header
Path Parameters
1 <= lengthRequest Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/projects/string/email/limit-increase" \ -H "Content-Type: application/json" \ -d '{ "requestedLimit": 1 }'{ "success": true, "data": { "id": "string", "status": "PENDING" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}The project's three sending ceilings, what it has sent against each, when the current quota month began, whether sending is paused and why, and any limit increase waiting for review.
Auth: a project API key carrying email:read.
Authorization
apiKey A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.
In: header
Path Parameters
1 <= lengthResponse Body
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/projects/string/email/limits"{ "success": true, "data": { "plan": "PAY_AS_YOU_GO", "includedEmails": -9007199254740991, "monthlyQuota": -9007199254740991, "dailyLimit": -9007199254740991, "hourlyLimit": -9007199254740991, "sentThisMonth": -9007199254740991, "sentLastDay": -9007199254740991, "sentLastHour": -9007199254740991, "periodStart": "2019-08-24T14:15:22Z", "pausedUntil": "2019-08-24T14:15:22Z", "pauseReason": "HOURLY_LIMIT", "pendingRequest": { "id": "string", "requestedLimit": -9007199254740991, "createdAt": "2019-08-24T14:15:22Z" } }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}Sets the most the project will send in one quota month. The change applies at once and keeps the current quota month.
On a plan with included emails, the cap starts at them, and raising it above them allows
the emails beyond the allowance, billed at the plan's per-email rate. On PAY_AS_YOU_GO
every email is billed, and the cap is the most a month can cost.
Raising the cap above what has been sent this month lifts a pause caused by reaching it. A pause from the hourly or daily limit is not lifted.
It changes what the project is billed, so only owners and admins can make it; anyone else is refused with 403 INSUFFICIENT_ROLE.
Auth: a project API key carrying all of email:read and email:write.
Role: the caller must be an owner or admin of the project.
Authorization
apiKey A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.
In: header
Path Parameters
1 <= lengthRequest Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
curl -X PUT "https://example.com/v1/projects/string/email/monthly-cap" \ -H "Content-Type: application/json" \ -d '{ "monthlyQuota": 100 }'{ "success": true, "data": { "plan": "PAY_AS_YOU_GO", "includedEmails": -9007199254740991, "monthlyQuota": -9007199254740991, "dailyLimit": -9007199254740991, "hourlyLimit": -9007199254740991, "sentThisMonth": -9007199254740991, "sentLastDay": -9007199254740991, "sentLastHour": -9007199254740991, "periodStart": "2019-08-24T14:15:22Z", "pausedUntil": "2019-08-24T14:15:22Z", "pauseReason": "HOURLY_LIMIT", "pendingRequest": { "id": "string", "requestedLimit": -9007199254740991, "createdAt": "2019-08-24T14:15:22Z" } }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}Moves the project to another email plan. The new plan is put on the project's subscription
before anything changes — one is started if the project has none — and a plan with a
monthly price is charged for the rest of the billing period straight away. A project that
cannot be billed is refused with 402 and stays on its current plan; see
Billing for the codes. Moving to PAY_AS_YOU_GO drops the monthly
price, crediting the unused part of the period, and bills every email from then on.
The monthly cap is reset to the new plan's included emails — or, on PAY_AS_YOU_GO, to a
starting budget — and the hourly and daily limits to the plan's, or to an approved limit
increase when that is higher. The current quota month is kept. Choosing the plan the
project is already on changes nothing.
It changes what the project is billed, so only owners and admins can make it; anyone else is refused with 403 INSUFFICIENT_ROLE.
Auth: a project API key carrying all of email:read and email:write.
Role: the caller must be an owner or admin of the project.
Authorization
apiKey A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.
In: header
Path Parameters
1 <= lengthRequest Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
curl -X PUT "https://example.com/v1/projects/string/email/plan" \ -H "Content-Type: application/json" \ -d '{ "plan": "PAY_AS_YOU_GO" }'{ "success": true, "data": { "plan": "PAY_AS_YOU_GO", "includedEmails": -9007199254740991, "monthlyQuota": -9007199254740991, "dailyLimit": -9007199254740991, "hourlyLimit": -9007199254740991, "sentThisMonth": -9007199254740991, "sentLastDay": -9007199254740991, "sentLastHour": -9007199254740991, "periodStart": "2019-08-24T14:15:22Z", "pausedUntil": "2019-08-24T14:15:22Z", "pauseReason": "HOURLY_LIMIT", "pendingRequest": { "id": "string", "requestedLimit": -9007199254740991, "createdAt": "2019-08-24T14:15:22Z" } }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}The project's bounce and complaint rates over a trailing window, the thresholds they are held to, and whether sending is suspended for them.
The project's biller is warned when either rate reaches its warning threshold. When either reaches
its suspension threshold, sending is suspended: SMTP refuses mail with a permanent 550 and
sending through the API answers 403 EMAIL_SENDING_SUSPENDED. A suspension does not lift on its own;
contact support to have it reviewed.
Auth: a project API key carrying email:read.
Authorization
apiKey A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.
In: header
Path Parameters
1 <= lengthResponse Body
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/projects/string/email/reputation"{ "success": true, "data": { "windowStart": "2019-08-24T14:15:22Z", "sent": -9007199254740991, "bounced": -9007199254740991, "complained": -9007199254740991, "bounceRate": 0, "complaintRate": 0, "level": "INSUFFICIENT_VOLUME", "suspendedAt": "2019-08-24T14:15:22Z", "suspensionReason": "COMPLAINT_RATE", "policy": { "windowDays": -9007199254740991, "minSent": -9007199254740991, "minEvents": -9007199254740991, "bounce": { "warning": 0, "suspend": 0 }, "complaint": { "warning": 0, "suspend": 0 } } }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}Sends one message, to up to 50 recipients, through an SMTP credential over HTTPS — no SMTP connection needed.
The credential's address is the From, and its email domain has to be ACTIVE. Each recipient counts as one
email against the project's limits and is billed as one; see Email pricing.
Give the content inline — subject, and html, text or both — or name a stored template in template
and leave all three out. A template is named by its alias or id; without template.version the published
version is sent, and with it any version the template still keeps. template.variables is checked against
the variables that version declares before anything is sent. The response then says which template and
version went out. See Email Templates.
Sending a template needs an API key carrying email-templates:read as well as the scopes below.
The checks run in order, and a refusal at any step sends nothing and uses none of the project's quota:
- the credential (
404) and its domain (400while it is notACTIVE); - billing (
402 EMAIL_BILLING_REQUIRED), a suspension (403 EMAIL_SENDING_SUSPENDED), and the hourly limit or a pause (400); - the template: no such template or version (
404 EMAIL_TEMPLATE_NOT_FOUND,404 EMAIL_TEMPLATE_VERSION_NOT_FOUND), never published (409 EMAIL_TEMPLATE_NOT_PUBLISHED), variables that do not match its declarations (422 EMAIL_TEMPLATE_VARIABLES_INVALID, with each problem inerror.details.issuesas{ path, message }), or a render that outgrew its limits (422 EMAIL_TEMPLATE_RENDER_FAILED).503 EMAIL_TEMPLATE_UNAVAILABLEmeans the template could not be loaded for a moment; nothing was sent, so retry; - the hourly and daily limits and the monthly cap, counting this send's recipients (
400 EMAIL_LIMIT_REACHED).
Both 503 codes — EMAIL_TEMPLATE_UNAVAILABLE and EMAIL_LIMITER_UNAVAILABLE, when sending is unavailable for
a moment — are answered before anything is sent, so they are safe to retry. Any other failure without an
answer, such as a timeout, may still have sent the message, and retrying it can deliver it twice.
Members with the viewer role are refused, as is a project blocked over an unpaid invoice (403 ACCOUNT_BLOCKED).
Auth: a project API key carrying all of email:read and email:write.
Rate limit: this endpoint is rate limited; a 429 means back off and retry.
Authorization
apiKey A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.
In: header
Path Parameters
1 <= lengthRequest Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/projects/string/email/send" \ -H "Content-Type: application/json" \ -d '{ "credentialId": "string", "to": "[email protected]" }'{ "success": true, "data": { "messageId": "string", "template": { "id": "string", "alias": "string", "version": -9007199254740991 } }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}How many emails the project has sent, and how many of them were delivered, bounced permanently and opened, with each as a share. Every event is dated by when its email was sent, so a delivery or open is counted in the same period as the send it belongs to.
Auth: a project API key carrying email:read.
Authorization
apiKey A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.
In: header
Path Parameters
1 <= lengthQuery Parameters
Only count email sent in this many trailing months. Leave it out to count every email ever sent.
Value in
- "3"
- "6"
- "12"
Response Body
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/projects/string/email/stats"{ "success": true, "data": { "since": "2019-08-24T14:15:22Z", "sent": -9007199254740991, "delivered": -9007199254740991, "bounced": -9007199254740991, "opened": -9007199254740991, "deliveryRate": 0, "bounceRate": 0, "openRate": 0 }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "issues": null, "docsUrl": "http://example.com" }}Domains
The domains in a project. A domain is registered through Cosmoner, moved in from another registrar, or external — a domain whose DNS you keep hosting elsewhere. An external domain starts `PENDING`, and the project's apps can use it only once you have proven you control it: publish the TXT record Verify ownership of an external domain returns, then call it again until the record is found. Turning email on for the domain and verifying its records proves the same thing. Keep the record published for as long as the domain is used here: it is checked again regularly, and an `ACTIVE` domain whose record stays missing for about a week goes back to `PENDING`, which stops its apps serving it. While the record is missing the domain stays `ACTIVE`, its owners and admins are notified, and calling Verify ownership again clears the warning once the record is back. If another project proves control of the domain while this project's record is missing, this project's domain goes back to `PENDING` at once. Search shows which names can be registered and what each costs; see [Domain pricing](/docs/pricing/domains). Registering a domain and moving one in are done in the control panel. Three calls work with an API key but are not described operation by operation yet, because they answer with the domain's full internal record, which is not a stable contract: listing the project's domains (`GET /v1/projects/{projectId}/domains`, `domains:read`), reading one by id or name (`GET /v1/projects/{projectId}/domains/{domain}`, `domains:read`) and adding an external domain (`POST /v1/projects/{projectId}/domains` with `{ "name": "example.com", "type": "EXTERNAL" }`, `domains:write` — `EXTERNAL` is the only type it takes). Rely only on `id`, `name`, `type`, `status`, `createdAt`, `dnsRecords` and, for an external domain, `verificationRecord` — the TXT record to publish — and `verificationLapsesAt`, which is set while an `ACTIVE` domain's record is missing and gives the earliest time it goes back to `PENDING`, and expect the rest to change. Adding a domain is refused to members with the viewer role, and to a project blocked over an unpaid invoice.
Email templates
Stored, versioned emails that a send names instead of carrying its own subject and HTML. A template is written in the control panel, either as a React Email component in TSX or in the visual editor, and declares the variables a send supplies. Publishing freezes the draft into a numbered version; Send an email uses the published version unless it names another. [Email Templates](/docs/guides/email-templates) explains writing, previewing and publishing them. A template is addressed by its id or its alias wherever `{templateId}` appears. Aliases are lower-case letters, digits, `-` and `_`, unique in the project, and can be changed — the id never does. The calls here read templates, versions and folders, manage a template's name, alias, folder and sample data, roll back to an earlier version, and delete. Editing a template's content, publishing a version and sending a test are done in the control panel's editor, which builds what a publish stores. Templates themselves are free; the emails sent from them are billed like any other. A project can hold up to 200 templates and 100 folders, and each template keeps its 50 most recent versions.