Docs
BlogHomeStart building

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.

ts
await totalum.email.send({ to: 'ana@example.com', subject: 'Welcome', html: '<p>Hi Ana</p>' });
Method or typeWhat it does
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.
email.sendSends one e-mail from the project's sender address: sign-up confirmations, receipts, notifications, password resets.
email.getAn e-mail's delivery status and history (queued, sent, delivered, bounced, …): show it on an admin page or retry a bounced address.
EmailSendInputOne e-mail, for email.send().
EmailSendOutputThe answer of email.send().
EmailViewAn e-mail and its delivery history, from email.get().

#totalumEmail

ts
totalumEmail(options?: TotalumClientOptions): 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.

Parameters

NameTypeRequiredDescription
optionsTotalumClientOptionsnoThe project key, API URL and environment; each falls back to its environment variable.
options.keystringnoProject key tlm_pk_…; default TOTALUM_PROJECT_KEY. Server-side only.
options.apiUrlstringnoThe Totalum API's URL: default TOTALUM_SDK_API_URL, else https://sdk-api.totalum.app.
options.env'dev' | 'live'noSent 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

ts
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

ts
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

NameTypeRequiredDescription
msgEmailSendInputyesThe recipients, subject, content and options.
msg.tostring | string[]yesThe recipient, or a list of recipients. Each is charged. Allowed: an e-mail address, at least 1 item.
msg.subjectstringyesThe subject line. Allowed: at least 1 character, at most 998 characters.
msg.htmlstringnoThe HTML body, sent byte for byte (inline your CSS). html or text is required.
msg.textstringnoThe plain-text body (shown by clients without HTML).
msg.from{ name?, localPart?, domain? }noThe sender's display name ({ name: "Acme" }). The address is the project's own; custom sender addresses are not available yet.
msg.from.namestringnoThe display name recipients see.
msg.from.localPartstringnoNot available yet (refused): the part before @.
msg.from.domainstringnoNot available yet (refused): a sender domain.
msg.replyTostring | string[]noWhere replies go: one address or a list. Allowed: an e-mail address, at least 1 item.
msg.ccstring | string[]noCarbon-copy recipients (each charged). Allowed: an e-mail address, at least 1 item.
msg.bccstring | string[]noBlind-copy recipients (each charged). Allowed: an e-mail address, at least 1 item.
msg.headersRecord<string, string>noExtra e-mail headers (List-Unsubscribe).
msg.attachments({ filename, url?, content?, contentType? })[]noFiles to attach, each from a url or base64 content. Allowed: at most 10 items.
msg.attachments.filenamestringyesThe attachment's file name. Allowed: at least 1 character.
msg.attachments.urlstringnoA URL to fetch the file from (a project file URL works). Allowed: a URL.
msg.attachments.contentstringnoThe file in base64, instead of url.
msg.attachments.contentTypestringnoIts MIME type.
msg.tags({ name, value })[]noLabels kept with the e-mail.
msg.tags.namestringyesThe tag name.
msg.tags.valuestringyesThe tag value.
msg.scheduledAtstringnoSend it later, at this time. Allowed: ISO 8601 date-time.
msg.idempotencyKeystringnoA key of yours: sending again with the same key does not send a second e-mail.
msg.bulkbooleannotrue 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.

FieldTypeAlways presentDescription
idstringyesThe e-mail's id (em_…): pass it to email.get().
status'queued' | 'sent'yesqueued (accepted, being sent) or sent.
message'Email sent successfully'yesAlways Email sent successfully.
successtrueyesAlways true (a failure throws).
messageIdstringyesThe delivery provider's message id.
usage{ credits }yesWhat the call cost.
usage.creditsnumberyesCredits charged for this call.

Errors — besides the errors every call can throw:

CodeHTTPWhen
DEV_RECIPIENT_NOT_ALLOWED403Only on Totalum's internal test environment (never for a customer project): a recipient that is not allow-listed.
VALIDATION_ERROR400A bad address, no html/text, more than 10 attachments, or a custom sender domain (not available yet).
EMAIL_SEND_FAILED502The e-mail could not be sent; the message says why.

Example

ts
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:

json
{
  "success": true,
  "message": "Email sent successfully",
  "messageId": "01a12c0c-9e5f-74ea-a128-a72d3fdd47ae",
  "id": "em_01M4P0S77M6XCM69SYZWP1XME9",
  "status": "queued",
  "usage": { "credits": 0.0515 }
}

#email.get

ts
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

NameTypeRequiredDescription
idstringyesThe 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:

CodeHTTPWhen
ENGINE_UNAVAILABLE500An unexpected error; retry.

Example

ts
const view = await totalum.email.get('em_01M4P0S77M6XCM69SYZWP1XME9');

Result:

json
{
  "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().

NameTypeRequiredDescription
tostring | string[]yesThe recipient, or a list of recipients. Each is charged. Allowed: an e-mail address, at least 1 item.
subjectstringyesThe subject line. Allowed: at least 1 character, at most 998 characters.
htmlstringnoThe HTML body, sent byte for byte (inline your CSS). html or text is required.
textstringnoThe plain-text body (shown by clients without HTML).
from{ name?, localPart?, domain? }noThe sender's display name ({ name: "Acme" }). The address is the project's own; custom sender addresses are not available yet.
from.namestringnoThe display name recipients see.
from.localPartstringnoNot available yet (refused): the part before @.
from.domainstringnoNot available yet (refused): a sender domain.
replyTostring | string[]noWhere replies go: one address or a list. Allowed: an e-mail address, at least 1 item.
ccstring | string[]noCarbon-copy recipients (each charged). Allowed: an e-mail address, at least 1 item.
bccstring | string[]noBlind-copy recipients (each charged). Allowed: an e-mail address, at least 1 item.
headersRecord<string, string>noExtra e-mail headers (List-Unsubscribe).
attachments({ filename, url?, content?, contentType? })[]noFiles to attach, each from a url or base64 content. Allowed: at most 10 items.
attachments.filenamestringyesThe attachment's file name. Allowed: at least 1 character.
attachments.urlstringnoA URL to fetch the file from (a project file URL works). Allowed: a URL.
attachments.contentstringnoThe file in base64, instead of url.
attachments.contentTypestringnoIts MIME type.
tags({ name, value })[]noLabels kept with the e-mail.
tags.namestringyesThe tag name.
tags.valuestringyesThe tag value.
scheduledAtstringnoSend it later, at this time. Allowed: ISO 8601 date-time.
idempotencyKeystringnoA key of yours: sending again with the same key does not send a second e-mail.
bulkbooleannotrue marks a marketing or bulk message (adds unsubscribe handling).

#EmailSendOutput

The answer of email.send().

FieldTypeAlways presentDescription
successtrueyesAlways true (a failure throws).
message'Email sent successfully'yesAlways Email sent successfully.
messageIdstringyesThe delivery provider's message id.
idstringyesThe e-mail's id (em_…): pass it to email.get().
status'queued' | 'sent'yesqueued (accepted, being sent) or sent.

#EmailView

An e-mail and its delivery history, from email.get().

FieldTypeAlways presentDescription
idstringyesThe e-mail's id.
fromstringyesThe sender, as recipients see it.
tostring[]yesThe recipients.
subjectstringyesThe subject.
status'queued' | 'failed' | 'sent' | 'delivered' | 'delayed' | 'bounced' | 'complained' | 'suppressed'yesThe 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).
lastEventAtstring | nullyesWhen the last event happened, or null.
events({ type, at, detail? })[]yesEvery delivery event, oldest first.
events.typestringyesThe event (email.sent, email.delivered, email.bounced, …).
events.atstringyesWhen it happened.
events.detailunknownnoExtra data of the event, when any.