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.
The project's templates, sorted by name, each with the version sends use. Filter by folder, or search by name or alias.
Auth: a project API key carrying email-templates: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 the templates directly in this folder, or root for those in no folder. Leave it out for every template.
1 <= lengthOnly templates whose name or alias contains this, ignoring case.
length <= 128Response Body
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/projects/string/email-templates"{ "success": true, "data": [ { "id": "string", "name": "string", "alias": "string", "description": "string", "editor": "CODE", "folderId": "string", "draftRevision": -9007199254740991, "latestVersionNumber": -9007199254740991, "createdAt": "2019-08-24T14:15:22Z", "updatedAt": "2019-08-24T14:15:22Z", "publishedVersion": { "id": "string", "version": -9007199254740991, "createdAt": "2019-08-24T14:15:22Z" } } ]}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}Creates a template with an unpublished draft — a short example unless source gives another. Nothing can be
sent from it until a version is published, which is done in the control panel's editor; until then a send
naming it is refused with 409 EMAIL_TEMPLATE_NOT_PUBLISHED.
A project can hold up to 200 templates; one more is refused with 409 EMAIL_TEMPLATE_LIMIT. A code template's
source is at most 256 KB and 50 files.
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-templates:read and email-templates: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-templates" \ -H "Content-Type: application/json" \ -d '{ "name": "string", "alias": "string", "editor": "CODE" }'{ "success": true, "data": { "id": "string", "name": "string", "alias": "string", "description": "string", "editor": "CODE", "folderId": "string", "draftRevision": -9007199254740991, "latestVersionNumber": -9007199254740991, "createdAt": "2019-08-24T14:15:22Z", "updatedAt": "2019-08-24T14:15:22Z", "draftSource": null, "previewData": { "property1": null, "property2": null }, "publishedVersion": { "id": "string", "version": -9007199254740991, "createdAt": "2019-08-24T14:15:22Z", "variables": { "property1": { "type": "string", "required": true, "description": "string", "sample": null, "default": null }, "property2": { "type": "string", "required": true, "description": "string", "sample": null, "default": null } } } }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}One template, by id or alias, with its draft, its saved sample data and the variables its published version declares.
Auth: a project API key carrying email-templates: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 <= lengthThe template's id or its alias.
1 <= length <= 64Response Body
application/json
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/projects/string/email-templates/string"{ "success": true, "data": { "id": "string", "name": "string", "alias": "string", "description": "string", "editor": "CODE", "folderId": "string", "draftRevision": -9007199254740991, "latestVersionNumber": -9007199254740991, "createdAt": "2019-08-24T14:15:22Z", "updatedAt": "2019-08-24T14:15:22Z", "draftSource": null, "previewData": { "property1": null, "property2": null }, "publishedVersion": { "id": "string", "version": -9007199254740991, "createdAt": "2019-08-24T14:15:22Z", "variables": { "property1": { "type": "string", "required": true, "description": "string", "sample": null, "default": null }, "property2": { "type": "string", "required": true, "description": "string", "sample": null, "default": null } } } }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}Renames the template, changes its alias or description, moves it to another folder, or saves its sample data. Fields left out are unchanged. None of it changes what a send renders.
Changing the alias takes effect at once: sends naming the old alias are refused with 404 from then on, and
sends by id are not affected. Sample data is at most 64 KB as JSON.
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-templates:read and email-templates: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 <= lengthThe template's id or its alias.
1 <= length <= 64Request 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 PATCH "https://example.com/v1/projects/string/email-templates/string" \ -H "Content-Type: application/json" \ -d '{}'{ "success": true, "data": { "id": "string", "name": "string", "alias": "string", "description": "string", "editor": "CODE", "folderId": "string", "draftRevision": -9007199254740991, "latestVersionNumber": -9007199254740991, "createdAt": "2019-08-24T14:15:22Z", "updatedAt": "2019-08-24T14:15:22Z", "draftSource": null, "previewData": { "property1": null, "property2": null }, "publishedVersion": { "id": "string", "version": -9007199254740991, "createdAt": "2019-08-24T14:15:22Z", "variables": { "property1": { "type": "string", "required": true, "description": "string", "sample": null, "default": null }, "property2": { "type": "string", "required": true, "description": "string", "sample": null, "default": null } } } }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}Deletes the template, its draft and every version. Sends naming it are refused with 404 from then on.
This cannot be undone.
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-templates:read and email-templates: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 <= lengthThe template's id or its alias.
1 <= length <= 64Response Body
application/json
application/json
application/json
application/json
application/json
application/json
curl -X DELETE "https://example.com/v1/projects/string/email-templates/string"{ "success": true, "data": null}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}The template's published versions, newest first, with the one sends use marked published. The 50 most
recent are kept; publishing past that removes the oldest, never the one sends use.
Auth: a project API key carrying email-templates: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 <= lengthThe template's id or its alias.
1 <= length <= 64Response Body
application/json
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/projects/string/email-templates/string/versions"{ "success": true, "data": [ { "id": "string", "version": -9007199254740991, "editor": "CODE", "sourceBytes": -9007199254740991, "compiledBytes": -9007199254740991, "compilerVersion": "string", "publishedByUserId": "string", "createdAt": "2019-08-24T14:15:22Z", "published": true } ]}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}Makes an earlier version — or a later one, to roll forward again — the one sends use when they name no version. It takes effect on the next send. The draft is not touched, and the next publish still gets the next version number.
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-templates:read and email-templates: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 <= lengthThe template's id or its alias.
1 <= length <= 64The version number.
0 < value <= 9007199254740991Response Body
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/projects/string/email-templates/string/versions/1/promote"{ "success": true, "data": { "id": "string", "version": -9007199254740991, "editor": "CODE", "sourceBytes": -9007199254740991, "compiledBytes": -9007199254740991, "compilerVersion": "string", "publishedByUserId": "string", "createdAt": "2019-08-24T14:15:22Z", "published": true }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}Every template folder in the project, sorted by name. Build the tree from each folder's parentId.
Auth: a project API key carrying email-templates: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-templates/folders"{ "success": true, "data": [ { "id": "string", "name": "string", "parentId": "string" } ]}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}Creates a folder, at the top level or inside another. A project can hold up to 100 folders, nested at most 5 levels deep, and folders that share a parent need different names.
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-templates:read and email-templates: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
application/json
curl -X POST "https://example.com/v1/projects/string/email-templates/folders" \ -H "Content-Type: application/json" \ -d '{ "name": "string" }'{ "success": true, "data": { "id": "string", "name": "string", "parentId": "string" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}Renames a folder, moves it under another folder or to the top level, or both. What it holds moves with it.
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-templates:read and email-templates: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 <= 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 PATCH "https://example.com/v1/projects/string/email-templates/folders/string" \ -H "Content-Type: application/json" \ -d '{}'{ "success": true, "data": { "id": "string", "name": "string", "parentId": "string" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}Deletes the folder and nothing in it: its templates and subfolders move up to the folder's own parent. When a subfolder's name is already taken there, the delete is refused with the clashing names and nothing changes — rename or move that subfolder first.
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-templates:read and email-templates: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
curl -X DELETE "https://example.com/v1/projects/string/email-templates/folders/string"{ "success": true, "data": null}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}{ "success": false, "error": { "code": "string", "message": "string", "details": { "property1": null, "property2": null }, "docsUrl": "http://example.com" }}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.
Storage
Object storage buckets and container registries. **Buckets** are S3-compatible: any S3 client or SDK reads and writes them, with an access key issued under [IAM](/docs/api/iam). Each bucket runs on a prepaid tier that includes a storage, egress and request allowance, and its usage is watched against it — a bucket that goes more than 10% over any part of its allowance is suspended until it moves to a larger tier. See [Object Storage](/docs/guides/object-storage) and [Object storage pricing](/docs/pricing/object-storage). **Container registries** hold the images apps deploy from. Push and pull with `docker`, logging in with a Cosmoner API key that carries `registry:read` to pull and `registry:write` to push; see [Container Registry](/docs/guides/container-registry) and [Container registry pricing](/docs/pricing/container-registry). Repository names are unique across the project, not just within a registry. A repository is private by default. A public one can be pulled by anyone without credentials, and must carry a monthly cap on that anonymous egress (`publicEgressCapGb`, 1–10,000 GB), because it is billed to the project; when the cap is reached the repository becomes private again and `publicEgressCapHitAt` records when. Making it public again, or raising the cap, starts a fresh allowance. Several calls work with an API key but are not described operation by operation yet, because they answer with the resource's full internal record, which is not a stable contract: listing buckets (`GET /v1/projects/{projectId}/storage/object-storage`, `object-storage:read`), listing registries with their repositories (`GET /v1/projects/{projectId}/storage/container-registry`, `registry:read`), reading one (`GET …/container-registry/{id}`), listing and reading its repositories (`GET …/container-registry/{id}/repositories` and `…/repositories/{repoId}`), creating a repository (`POST …/container-registry/{id}/repositories` with `name` and optionally `visibility` and `publicEgressCapGb`, `registry:write`) and updating a repository's retention and visibility (`PATCH …/repositories/{repoId}`, `registry:write`). Rely only on `id`, `name`, `region`, `status`, `tier`, `publicAccess`, `publicPaths`, `versioning`, `corsRules`, `cdnEnabled`, `cdnDomain` and `createdAt` for a bucket; `id`, `name`, `region`, `status` and `createdAt` for a registry; and `id`, `name`, `fullPath`, `visibility`, `publicEgressCapGb`, `publicEgressUsedGb`, `publicEgressCapHitAt`, the `gc…` retention settings and `createdAt` for a repository — and expect the rest to change. Creating and updating a repository are refused to members with the viewer role, and to a project blocked over an unpaid invoice. Creating a bucket or a registry, previewing what one costs, changing a registry's settings, turning a bucket's CDN on or off, browsing a bucket's files and seeing which access keys reach a bucket or repository are done in the control panel for now.