Documentation
ReferenceJavaScript SDKNamespacescomms

comms

Sending email and SMS through your own providers, from templates you own.

Server client only. comms is not available on createClient — a publishable key must never be able to send messages.

Your code names a template and a language. Which provider carries it, and what the message actually says, are configuration — so changing either is not a deploy.

Sending is queued

A call that resolves without an error means the message was accepted for delivery, not delivered. The call publishes; a worker sends. Anything that happens after that point is recorded on the message, not returned to you — see Sending is queued.

email.send

email.send(params) → null

const { data, error } = await nulvless.comms.email.send({
  to: '[email protected]',
  template: 'grants-invite',
  language: 'pt-BR',
  params: { organization: 'Acme Inc', role: 'admin' },
});
tostringRequired

Recipient address.

templatestringRequired

The slug. Must exist for this channel and this language.

languageLanguageOptional

Which language variant to render. Falls back to the environment default when omitted.

paramsRecord<string, unknown>Optional

Variables injected into the template. A template that expects a variable you did not supply fails at delivery, not at this call.

sms.send

sms.send(params) → null

The same shape, with phone instead of to.

await nulvless.comms.sms.send({
  phone: { country_code: '+55', number: '11987654321' },
  template: 'phone-otp-verification',
  language: 'en-US',
  params: { code: '492013' },
});
phonePhoneRequired

Country code and number, not a formatted string.

templatestringRequired
languageLanguageOptional
paramsRecord<string, unknown>Optional

Sends are quota-limited per channel. Exhausting it answers 429 rate_limit_exceeded — with no retryAfter, unlike the auth service, so keep your own backoff.

email.test and sms.test

test(recipient) → null

Sends a probe so you can confirm the provider is wired before a real message depends on it.

await nulvless.comms.email.test('[email protected]');

Worth running from your deploy pipeline against a fresh environment — a misconfigured provider is otherwise invisible until the first user needs a verification code.

Built-in templates

Every environment starts with the templates the platform itself needs. These are the slugs, and each is editable:

EmailSMS
welcome-userphone-otp-verification
email-otp-verificationphone-link-verification
email-link-verificationphone-otp-enable-mfa-request
email-otp-enable-mfa-requestchange-phone-identity
change-email-identity
reset-password-email
grants-invite

A template is per channel: the email and SMS versions of the same idea are separate slugs, because wording that works in an inbox does not survive 160 characters.

templates

list · get · create · update · delete

await nulvless.comms.templates.list({ channel: 'email' });
await nulvless.comms.templates.get('email', 'welcome-user', 'pt-BR');
await nulvless.comms.templates.create(params);
await nulvless.comms.templates.update('email', 'welcome-user', params, 'pt-BR');
await nulvless.comms.templates.delete('email', 'welcome-user', 'pt-BR');

Everything but create and list is addressed by channel + slug + language. That triple is the identity of a template — there is no id to hold onto, and the same slug in two languages is two records.

Most teams edit these in the console. The API exists for when you version them alongside your application.

create

slugstringRequired

What your code will name.

channelNotificationChannelRequired
emailRequires subject.
smsBody only.
webhookRequires url — posts instead of rendering a message.
languageTemplateLanguageRequired

en-US or pt-BR.

contentstringRequired

The body.

subjectstringOptional

Required for the email channel.

urlstringOptional

Required for the webhook channel.

namestringOptional
descriptionstringOptional
configRecord<string, unknown>Optional

update takes the same optional fields minus slug, channel and language — those identify the template rather than describe it.

The webhook channel

Besides email and SMS a template can be a webhook: instead of rendering a message for a person, it posts to a url you own. The call site is identical — same slug, same params — so a notification can move from email to a webhook as configuration, without touching the code that triggers it.

The Template object

idstringRequired
slugstringRequired
channelNotificationChannelRequired
languageTemplateLanguageRequired
contentstringRequired
subjectstringOptional
Email only.
urlstringOptional
Webhook only.
commonbooleanRequired

true for a platform-level built-in, false for one you created. Useful for showing which templates came with the environment.

last_submissionstringOptional
created_atstringRequired
updated_atstringRequired

Why name a template

Naming a template instead of composing a message is what makes the rest work:

  • Per language — the same call renders pt-BR or en-US from one slug.
  • Per environment — development points at a sandbox provider, so a test never reaches a real inbox.
  • Per channel — email and SMS versions of one message are edited side by side.

None of that requires the calling code to change.

Next

On this page