Email (totalum-sdk/email)
import … from 'totalum-sdk/email' — or the same namespace on totalum from 'totalum-sdk'. Install, configuration and errors: Totalum SDK.
Send transactional e-mail (sign-up confirmations, receipts, notifications) from the project's own sender, and check whether it was delivered. The HTML you pass is sent byte for byte. Billed in credits per recipient.
await totalum.email.send({ to: 'ana@example.com', subject: 'Welcome', html: '<p>Hi Ana</p>' });| Method or type | What it does |
|---|---|
totalumEmail | Creates the email namespace (the same object as totalum.email, scope email): HTML sent byte for byte, charged per recipient, sent from the project's own address p-<project>@mail.totalum-project.com. |
email.send | Sends one e-mail from the project's sender address: sign-up confirmations, receipts, notifications, password resets. |
email.get | An e-mail's delivery status and history (queued, sent, delivered, bounced, …): show it on an admin page or retry a bounced address. |
EmailSendInput | One e-mail, for email.send(). |
EmailSendOutput | The answer of email.send(). |
EmailView | An e-mail and its delivery history, from email.get(). |
#totalumEmail
totalumEmail(options?: TotalumClientOptions): TotalumEmailCreates the email namespace (the same object as totalum.email, scope email): HTML sent byte for byte, charged
per recipient, sent from the project's own address p-<project>@mail.totalum-project.com.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
options | TotalumClientOptions | no | The project key, API URL and environment; each falls back to its environment variable. |
options.key | string | no | Project key tlm_pk_…; default TOTALUM_PROJECT_KEY. Server-side only. |
options.apiUrl | string | no | The Totalum API's URL: default TOTALUM_SDK_API_URL, else https://sdk-api.totalum.app. |
options.env | 'dev' | 'live' | no | Sent as x-totalum-env on every call (the database it targets, and dev vs live usage); default TOTALUM_ENV, else live. |
Returns TotalumEmail — The email namespace: the methods below.
Example
import { totalumEmail } from 'totalum-sdk/email';
const email = totalumEmail();
await email.send({ to: 'ana@example.com', subject: 'Welcome', html: '<p>Hi Ana</p>' });The namespace's type is exported as TotalumEmail (import type { TotalumEmail } from 'totalum-sdk/email').
#email.send
email.send(msg: EmailSendInput): Promise<{ id, status, message, success, messageId, usage }>Sends one e-mail from the project's sender address: sign-up confirmations, receipts, notifications, password
resets. The HTML is sent byte for byte (inline your CSS); add text for clients that show plain text. Charged
per recipient (usage.credits). Keep the returned id to check delivery with get(id).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
msg | EmailSendInput | yes | The recipients, subject, content and options. |
msg.to | string | string[] | yes | The recipient, or a list of recipients. Each is charged. Allowed: an e-mail address, at least 1 item. |
msg.subject | string | yes | The subject line. Allowed: at least 1 character, at most 998 characters. |
msg.html | string | no | The HTML body, sent byte for byte (inline your CSS). html or text is required. |
msg.text | string | no | The plain-text body (shown by clients without HTML). |
msg.from | { name?, localPart?, domain? } | no | The sender's display name ({ name: "Acme" }). The address is the project's own; custom sender addresses are not available yet. |
msg.from.name | string | no | The display name recipients see. |
msg.from.localPart | string | no | Not available yet (refused): the part before @. |
msg.from.domain | string | no | Not available yet (refused): a sender domain. |
msg.replyTo | string | string[] | no | Where replies go: one address or a list. Allowed: an e-mail address, at least 1 item. |
msg.cc | string | string[] | no | Carbon-copy recipients (each charged). Allowed: an e-mail address, at least 1 item. |
msg.bcc | string | string[] | no | Blind-copy recipients (each charged). Allowed: an e-mail address, at least 1 item. |
msg.headers | Record<string, string> | no | Extra e-mail headers (List-Unsubscribe). |
msg.attachments | ({ filename, url?, content?, contentType? })[] | no | Files to attach, each from a url or base64 content. Allowed: at most 10 items. |
msg.attachments.filename | string | yes | The attachment's file name. Allowed: at least 1 character. |
msg.attachments.url | string | no | A URL to fetch the file from (a project file URL works). Allowed: a URL. |
msg.attachments.content | string | no | The file in base64, instead of url. |
msg.attachments.contentType | string | no | Its MIME type. |
msg.tags | ({ name, value })[] | no | Labels kept with the e-mail. |
msg.tags.name | string | yes | The tag name. |
msg.tags.value | string | yes | The tag value. |
msg.scheduledAt | string | no | Send it later, at this time. Allowed: ISO 8601 date-time. |
msg.idempotencyKey | string | no | A key of yours: sending again with the same key does not send a second e-mail. |
msg.bulk | boolean | no | true marks a marketing or bulk message (adds unsubscribe handling). |
Returns Promise<{ id, status, message, success, messageId, usage }> — The e-mail's id, its status and what it cost.
| Field | Type | Always present | Description |
|---|---|---|---|
id | string | yes | The e-mail's id (em_…): pass it to email.get(). |
status | 'queued' | 'sent' | yes | queued (accepted, being sent) or sent. |
message | 'Email sent successfully' | yes | Always Email sent successfully. |
success | true | yes | Always true (a failure throws). |
messageId | string | yes | The delivery provider's message id. |
usage | { credits } | yes | What the call cost. |
usage.credits | number | yes | Credits charged for this call. |
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
DEV_RECIPIENT_NOT_ALLOWED | 403 | Only on Totalum's internal test environment (never for a customer project): a recipient that is not allow-listed. |
VALIDATION_ERROR | 400 | A bad address, no html/text, more than 10 attachments, or a custom sender domain (not available yet). |
EMAIL_SEND_FAILED | 502 | The e-mail could not be sent; the message says why. |
Example
const sent = await totalum.email.send({
to: 'ana@example.com',
subject: 'Your invoice F-2026-0012',
html: '<p>Hi Ana,</p><p>Your invoice is attached.</p>',
text: 'Hi Ana, your invoice is attached.',
attachments: [{ filename: 'F-2026-0012.pdf', url: invoice.url }],
replyTo: 'billing@acme.com',
});Result:
{
"success": true,
"message": "Email sent successfully",
"messageId": "01a12c0c-9e5f-74ea-a128-a72d3fdd47ae",
"id": "em_01M4P0S77M6XCM69SYZWP1XME9",
"status": "queued",
"usage": { "credits": 0.0515 }
}#email.get
email.get(id: string): Promise<EmailView | null>An e-mail's delivery status and history (queued, sent, delivered, bounced, …): show it on an admin page or retry a bounced address. Free.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | The id send() returned (em_…). |
Returns Promise<EmailView \| null> — The e-mail and its events, or null when it is not this project's.
Errors — besides the errors every call can throw:
| Code | HTTP | When |
|---|---|---|
ENGINE_UNAVAILABLE | 500 | An unexpected error; retry. |
Example
const view = await totalum.email.get('em_01M4P0S77M6XCM69SYZWP1XME9');Result:
{
"id": "em_01M4P0S77M6XCM69SYZWP1XME9",
"from": "\"Acme\" <p-acme-crm@mail.totalum-project.com>",
"to": ["ana@example.com"],
"subject": "Your invoice F-2026-0012",
"status": "delivered",
"lastEventAt": "2026-10-11T17:39:02.000Z",
"events": [
{ "type": "email.sent", "at": "2026-10-11T17:38:58.000Z" },
{ "type": "email.delivered", "at": "2026-10-11T17:39:02.000Z" }
]
}#EmailSendInput
One e-mail, for email.send().
| Name | Type | Required | Description |
|---|---|---|---|
to | string | string[] | yes | The recipient, or a list of recipients. Each is charged. Allowed: an e-mail address, at least 1 item. |
subject | string | yes | The subject line. Allowed: at least 1 character, at most 998 characters. |
html | string | no | The HTML body, sent byte for byte (inline your CSS). html or text is required. |
text | string | no | The plain-text body (shown by clients without HTML). |
from | { name?, localPart?, domain? } | no | The sender's display name ({ name: "Acme" }). The address is the project's own; custom sender addresses are not available yet. |
from.name | string | no | The display name recipients see. |
from.localPart | string | no | Not available yet (refused): the part before @. |
from.domain | string | no | Not available yet (refused): a sender domain. |
replyTo | string | string[] | no | Where replies go: one address or a list. Allowed: an e-mail address, at least 1 item. |
cc | string | string[] | no | Carbon-copy recipients (each charged). Allowed: an e-mail address, at least 1 item. |
bcc | string | string[] | no | Blind-copy recipients (each charged). Allowed: an e-mail address, at least 1 item. |
headers | Record<string, string> | no | Extra e-mail headers (List-Unsubscribe). |
attachments | ({ filename, url?, content?, contentType? })[] | no | Files to attach, each from a url or base64 content. Allowed: at most 10 items. |
attachments.filename | string | yes | The attachment's file name. Allowed: at least 1 character. |
attachments.url | string | no | A URL to fetch the file from (a project file URL works). Allowed: a URL. |
attachments.content | string | no | The file in base64, instead of url. |
attachments.contentType | string | no | Its MIME type. |
tags | ({ name, value })[] | no | Labels kept with the e-mail. |
tags.name | string | yes | The tag name. |
tags.value | string | yes | The tag value. |
scheduledAt | string | no | Send it later, at this time. Allowed: ISO 8601 date-time. |
idempotencyKey | string | no | A key of yours: sending again with the same key does not send a second e-mail. |
bulk | boolean | no | true marks a marketing or bulk message (adds unsubscribe handling). |
#EmailSendOutput
The answer of email.send().
| Field | Type | Always present | Description |
|---|---|---|---|
success | true | yes | Always true (a failure throws). |
message | 'Email sent successfully' | yes | Always Email sent successfully. |
messageId | string | yes | The delivery provider's message id. |
id | string | yes | The e-mail's id (em_…): pass it to email.get(). |
status | 'queued' | 'sent' | yes | queued (accepted, being sent) or sent. |
#EmailView
An e-mail and its delivery history, from email.get().
| Field | Type | Always present | Description |
|---|---|---|---|
id | string | yes | The e-mail's id. |
from | string | yes | The sender, as recipients see it. |
to | string[] | yes | The recipients. |
subject | string | yes | The subject. |
status | 'queued' | 'failed' | 'sent' | 'delivered' | 'delayed' | 'bounced' | 'complained' | 'suppressed' | yes | The latest state: queued, sent, delivered, delayed, bounced (the address does not exist), complained (marked as spam), failed, suppressed (not sent: the address bounced or complained before). |
lastEventAt | string | null | yes | When the last event happened, or null. |
events | ({ type, at, detail? })[] | yes | Every delivery event, oldest first. |
events.type | string | yes | The event (email.sent, email.delivered, email.bounced, …). |
events.at | string | yes | When it happened. |
events.detail | unknown | no | Extra data of the event, when any. |
