TypeScript Library v3.00

tnzapi-ts

tnzapi-ts is TNZ's official TypeScript/Node.js library for sending SMS, Email, TTS, Voice, Fax, WhatsApp, RCS, and Workflow messages, and for managing your Addressbook and OptOut list, distributed via npm. Install it and skip writing your own HTTP client, request signing, and response parsing against the TNZ REST API. Send your first message in a few lines of TypeScript or plain JavaScript.

Quick Example

import { TNZAPI } from 'tnzapi-ts';

const client = new TNZAPI({ AuthToken: "[Your Auth Token]" });

const response = await client.Messaging.SMS.SendMessage({
    Message: "Test SMS",
    ToNumber: "+64211111111",
    Mode: "Test"
});

if (response.Result === "Success") {
    console.log(`Success - MessageID: ${response.MessageID}`);
}

See Getting Started below for how to get an Auth Token.

What you can do

Send & receive messages:

  • Workflow: trigger multi-channel, no-code automation templates
  • SMS: two-way text messaging
  • Email: plain-text or HTML, with attachments
  • TTS: text-to-speech voice calls
  • Voice: pre-recorded audio calls
  • Fax: document delivery
  • WhatsApp: template messaging
  • RCS: Rich Communication Services

Manage your account:

  • Addressbook: contacts, groups, and relationships
  • OptOut: manage opted-out destinations
  • Webhooks: receive inbound delivery/reply events on your own server
  • Reports: poll message status, SMS replies, and received SMS
  • Actions: reschedule, abort, resubmit, or adjust pacing on an in-flight job, for any channel, from one place

Supported Node.js versions

tnzapi-ts is a Node.js library: it talks to the TNZ API using Node's built-in http/https modules, and reads local files via fs for attachments. It does not run in a browser: bundlers targeting browser output (webpack, Vite, Create React App) can't resolve these Node built-ins.

  • Node.js 14.x or later (LTS recommended). The compiled output uses native optional chaining (?.), which requires Node 14+.
  • Module format: CommonJS (require/module.exports) with generated .d.ts declarations. Also works from ESM projects ("type": "module" in package.json) via Node's standard CommonJS interop: import { TNZAPI } from 'tnzapi-ts' works either way.
  • TypeScript: no minimum version enforced, developed and tested against TypeScript 5.4+. Plain JavaScript projects, CommonJS or ESM, can use the library too, since types are optional, not required.

Because it's Node-only, tnzapi-ts works in any framework or runtime that executes JavaScript/TypeScript server-side:

Framework / runtimeSupportedNotes
Node.js (plain scripts, ts-node)
Express
Fastify
Koa
NestJS
Next.js✓ (server only)API routes, route handlers, server components, and server actions. Not Client Components ("use client"), which execute in the browser.
Remix / React Router (framework mode)✓ (server only)Loaders and actions only, not browser-rendered components.
AWS Lambda / Azure Functions / Google Cloud FunctionsNode.js runtime required.
Electron✓ (main process only)Not the renderer process, unless nodeIntegration is enabled.
Browser (client-side React/Vue/Angular, plain <script>)Not supportedNo fs/http/https in the browser: bundling fails, or requires broken polyfills.

Installation

Install the package from npm:

npm install tnzapi-ts

Alternatively, you can browse the source code on GitHub. The repository includes samples/, covering Actions, Addressbook, Messaging, OptOut, and Reports usage, and demo/, a full API and web demo project (demo/api plus a shared demo/web frontend).

Continue to Getting Started to create your first TNZAPI client.

Getting Started

Create a TNZAPI client to start using the TNZ API.

Register an Account

If you don't already have a TNZ account, sign up here before continuing.

API Credentials

tnzapi-ts authenticates every request with a JWT Auth Token.

Export your Auth Token

  1. Login to the TNZ Dashboard
  2. Navigate to 'Users'
  3. Create a new user or select an existing one
  4. Enable API access (if it's not already enabled)
  5. Click on the 'API' tab
  6. Enable 'Auth Token' and create a new Auth Token
  7. Click the 'Copy' button to copy it to your clipboard

Pass it directly to the TNZAPI constructor:

import { TNZAPI } from 'tnzapi-ts';

const client = new TNZAPI({ AuthToken: "[Your Auth Token]" });

The TNZAPI constructor accepts AuthToken directly, and builds every facade (.Messaging, .Reports, .Actions, .Addressbook, .OptOut) up front. There's only the one construction pattern.

Auth Token requirement

An Auth Token has to come from one source or the other. If neither AuthToken nor the TNZ_AUTH_TOKEN environment variable is set, new TNZAPI() throws synchronously:

Error: TNZ AuthToken is required. Pass it as AuthToken or set the TNZ_AUTH_TOKEN environment variable.

This is the one place in the SDK that throws. Every other call still resolves its returned Promise, on failure as well as success: a missing field, an unauthorized token, or a network failure resolves to an error response object rather than rejecting or throwing (see Response basics below).

Refresh or invalidate your Auth Token

  1. Login to the TNZ Dashboard
  2. Navigate to 'Users'
  3. Click on your API user
  4. Click on the 'API' tab
  5. Click the refresh/recycle button in the Auth Token section
  6. Update your applications to use the new Auth Token

Refreshing invalidates the old token immediately. Any application still using it will start failing authentication until it's updated with the new one.

Environment Variables

tnzapi-ts can also be configured via environment variables (useful for CI, containers, or keeping credentials out of source control). It reads OS-level environment variables only: it doesn't load a .env file itself, so use a package such as dotenv if you want one loaded in a plain Node.js script.

VariablePurposeDefault
TNZ_AUTH_TOKENFallback Auth Token used whenever AuthToken isn't passed explicitly to the constructor. An explicit AuthToken always takes precedence.(none: constructor throws if still empty)
TNZ_API_URLOverrides the API base URL the client sends requests to. Passing URL to the constructor takes precedence over this.https://api.tnz.co.nz/api/v3.00
TNZ_UNSAFE_IGNORE_SSLMust be the exact lowercase string "true" to bypass TLS certificate verification. Requests fail (resolving as an error response rather than being sent) whenever NODE_ENV=production is also set, since accepting forged or invalid certificates would still send the Authorization bearer token over a connection an attacker controls.(unset, certificates verified)
TNZ_ALLOW_INSECURE_HTTPMust be the exact lowercase string "true" to allow requests over plain HTTP (useful when pointing TNZ_API_URL at a local/staging server without TLS). By default the client refuses to send the Authorization bearer token over anything but HTTPS.(unset, HTTPS enforced)
import { TNZAPI } from 'tnzapi-ts';

// Picks up TNZ_AUTH_TOKEN automatically since no AuthToken is set explicitly
const client = new TNZAPI();

Response basics

Every method returns a Promise that resolves to one of two shapes: on success, an object with Result: "Success" plus whatever fields that endpoint returns (e.g. MessageID); on failure, an object shaped like ErrorResponseDTO, with Result set to "Error", "Failed", or "Unauthorized", and ErrorMessage always a string[] (even when empty).

const result = await client.Messaging.SMS.SendMessage({
    Message: "Hello from tnzapi-ts",
    ToNumber: "+64211111111",
});

if (result.Result === "Success") {
    console.log(`Sent - MessageID: ${result.MessageID}`);
} else {
    for (const error of result.ErrorMessage) {
        console.log(`- Error=${error}`);
    }
}

Check result.Result === "Success" rather than result.Result !== "Error". A failed response's Result is typed as "Error" | "Failed" | "Unauthorized", and which of the three you get back depends on what the server itself returned; a transport-level failure the client catches itself (an unreachable host, a request timeout, a non-JSON response) falls back to "Error". Narrowing on the success case is the one check that covers every case.

The Result enum backing these values (Result.Success, Result.Failed, and so on) is used internally, and shows up in exported type signatures like ErrorResponseDTO's Result field, but the enum itself isn't re-exported from 'tnzapi-ts'. Compare against the plain string literals shown above, not an imported enum member.

Common response enums

Unlike some of TNZ's other SDKs, several request fields here are backed by real TypeScript enums, importable directly from 'tnzapi-ts':

import { WebhookCallbackFormat, TTSVoice } from 'tnzapi-ts';
EnumMembersUsed for
WebhookCallbackFormatJSON, XML, POST, GETThe WebhookCallbackFormat request field, controlling how inbound delivery/reply events are posted to your WebhookCallbackURL.
NotificationTypeNone, Webhook, EmailThe NotificationType request field.
AnswerPhoneModeNDAS, NDAF, DAS, DAFAnswering-machine handling on TTS/Voice calls.
TTSVoiceFemale1, Male1, Nicole, Russell, Amy, Brian, EmmaTTS's Voice field.
FaxResolutionLow, HighFax's Resolution field.
SMSFallbackModeNone, RCS, WAPP, VoiceSMS's FallbackMode field.
WhatsAppFallbackModeNone, RCS, SMS, VoiceWhatsApp's FallbackMode field.
RCSFallbackModeNone, SMS, Voice, WAPPRCS's FallbackMode field.

Mode, the Test/Live field present on every channel's request args, is not one of these enums: it's typed as the plain string literal 'Test', so Mode: "Test" is the only form it takes. No enum import is needed for it.

Result is the one exception among these: it's a real enum too (Success | Failed | Error | Unauthorized), but as noted in Response basics above, it isn't exported from the package root, so compare against the plain string values instead of importing it.

The destination model

Unlike tnzapi-python or TNZAPI.NET, there is no shared Destination class to construct here. Each channel declares its own plain interface for its destinations (ISMSDestination, IEmailDestination, IWorkflowDestination, and so on), and you pass plain object literals directly:

const destinations = [
    { ToNumber: "+64211111111", FirstName: "Alice" },
    { ToNumber: "+64221111111", FirstName: "Bob" },
];

IMessagingDestination is also exported, as a union of every channel's destination interface, but it's a convenience type only: it isn't the parameter type any single channel actually declares for its own Destinations array.

Every channel also accepts a single-recipient shorthand as a top-level argument, instead of a full Destinations array, each accepting comma-separated values to target multiple recipients at once. SMS, for example, accepts top-level ToNumber, GroupID, and ContactID fields:

const result = await client.Messaging.SMS.SendMessage({
    Message: "Hello",
    ToNumber: "+64211111111,+64221111111",
});

Each channel's own section documents its specific shorthand field(s) and destination shape.

Workflow

Workflow triggers a pre-configured messaging strategy with a single API call. Build the strategy once in the Dashboard's drag-and-drop builder, then trigger it from your application using this library, with no code changes needed when the strategy itself changes. A Workflow Template can chain channels together: start with an SMS, fail over to an Email if there's no reply, then trigger a TTS call if the email goes unread. It can define fallback channels so a message keeps trying until it gets through, and fire custom webhooks at any stage.

Workflow's typed arguments (IWorkflowArgs) have no Message or TemplateID text content field, no ReportTo, no NotificationType, and no Attachments: every other messaging channel in this library declares all four. Workflow's SendMessage(...) and the inherited AddRecipient(...) builder method are the only ways to trigger it.

Quick Example

const response = await client.Messaging.Workflow.SendMessage({
    WorkflowTemplateID: "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    ToNumber: "+64211111111"
});

if (response.Result === "Success") {
    console.log(`Success - MessageID: ${response.MessageID}`);
}

Parameters

ParameterTypeRequiredDescription
WorkflowTemplateIDstring (uuid)YesID of the Workflow Template to trigger, built in the Dashboard.
DestinationsIWorkflowDestination[]No*One or more destinations. See Destination Fields below.
ToNumberstringNo*Single-destination shorthand for a phone number, e.g. "+64211111111". Comma-separated values create multiple destinations.
MainPhonestringNo*Single-destination shorthand for a secondary/main phone number. A separate field from ToNumber, not an alias of it: setting both adds two destinations. Comma-separated values create multiple destinations.
GroupIDstringNo*Single addressbook group shorthand. Comma-separated values create multiple destinations.
ContactIDstringNo*Single addressbook contact shorthand. Comma-separated values create multiple destinations.
ReferencestringNoYour internal reference, returned in reports and webhooks.
SendTimestringNoSchedule the trigger, e.g. "2026-09-01 09:00". Combine with Timezone.
TimezonestringNoWindows timezone name for SendTime, e.g. "New Zealand".
SubAccountstringNoSub-account code for billing separation.
DepartmentstringNoDepartment code.
ChargeCodestringNoCharge code for billing and reporting.
MessageIDstringNoSupply your own message ID, otherwise one is auto-generated.
WebhookCallbackURLstringNoURL for delivery status callbacks. Requires WebhookCallbackFormat to also be set.
WebhookCallbackFormatWebhookCallbackFormatYes‡Callback format: "JSON", "XML", "POST", or "GET".
Mode'Test'NoSet to "Test" to validate the request without triggering the Workflow. Any other value is rejected before the request is sent.

*At least one of Destinations, ToNumber, MainPhone, GroupID, or ContactID must be set. SendMessage(...) resolves ToNumber, MainPhone, GroupID, and ContactID into Destinations entries before validating, splitting comma-separated values into one destination each, so any one of them on its own satisfies the requirement, and all of them combine additively with an explicit Destinations array rather than replacing it. The original shorthand fields are never sent to the API themselves, only the Destinations entries they resolve into.
‡Only required when WebhookCallbackURL is set; otherwise the request is rejected with Result: "Error".

Destination Fields (IWorkflowDestination)

Workflow destinations are plain object literals matching the IWorkflowDestination interface: there is no shared Destination class to import or construct. Pass object literals directly inside Destinations, or add them one at a time with AddRecipient(...). Unlike every other channel's destination shape in this library, ToNumber, MainPhone, and EmailAddress can all be set on the same destination at once, letting one Workflow Template route to whichever channel(s) it's configured for. There is no FaxNumber or MobilePhone field on IWorkflowDestination.

FieldDescription
ContactIDAddressbook contact reference. Sends to that contact instead of a raw address.
GroupIDAddressbook group reference. Sends to all members of that group.
GroupCodeAlternative group lookup by code.
ToNumberPhone destination, e.g. "+64211111111". Creates or updates an addressbook contact inline unless paired with ContactID/GroupID. Can be set alongside MainPhone and EmailAddress on the same destination for omni-channel Workflow Templates.
MainPhoneA separate field from ToNumber, letting a Workflow Template distinguish the two. Creates or updates an addressbook contact inline unless paired with ContactID/GroupID. Can be set alongside ToNumber and EmailAddress for omni-channel routing.
EmailAddressEmail destination. Creates or updates an addressbook contact inline unless paired with ContactID/GroupID. Can be set alongside ToNumber/MainPhone on the same destination for omni-channel Workflow Templates. This is the only way to route a Workflow send to email: there is no top-level EmailAddress shorthand.
RecipientGeneric fallback field, used internally by AddRecipient(string) when adding a bare string.
AttentionPersonalisation token override [[Attention]].
FirstNamePersonalisation token override [[FirstName]].
LastNamePersonalisation token override [[LastName]].
CompanyPersonalisation token override [[Company]].
Custom1Custom9Personalisation token overrides [[Custom1]][[Custom9]], passed through to whichever channel(s) the Workflow Template actually uses.

Code Samples

Single destination shorthand

Trigger a Workflow Template for one phone destination with the fewest arguments possible.

const response = await client.Messaging.Workflow.SendMessage({
    WorkflowTemplateID: "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    ToNumber: "+64211111111"
});

Via the builder

All messaging classes also expose a chainable builder API: AddRecipient(...) followed by SendMessage({...}). Prefer the object-argument form of SendMessage(...) above for typed destination objects; the builder is most useful for adding simple phone-number strings one at a time.

const response = await client.Messaging.Workflow
    .AddRecipient("+64211111111")
    .SendMessage({
        WorkflowTemplateID: "a1b2c3d4-e5f6-7890-1234-567890abcdef"
    });

Destination with multiple channel fields set

Set ToNumber, MainPhone, and EmailAddress all on the same destination, plus personalisation fields. This is the pattern that makes Workflow genuinely omni-channel, and the only way to reach an email address on a Workflow send, since there is no top-level EmailAddress shorthand (see the callout above). The Template picks whichever address(es) it needs from the same destination.

const response = await client.Messaging.Workflow.SendMessage({
    WorkflowTemplateID: "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    Destinations: [{
        ToNumber: "+64211111111",
        MainPhone: "+6491112222",
        EmailAddress: "test@example.com",
        FirstName: "Alice",
        Custom1: "Account #4432"
    }]
});

Multiple destinations

Trigger the same Workflow Template for more than one destination in a single request, personalising each with its own destination fields.

const response = await client.Messaging.Workflow.SendMessage({
    WorkflowTemplateID: "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    Destinations: [
        {
            ToNumber: "+64211111111",
            FirstName: "Alice",
            Company: "Example Company",
            Custom1: "Account #4432"
        },
        {
            ToNumber: "+64222222222",
            FirstName: "Bob",
            Company: "Example Company",
            Custom1: "Account #7788"
        }
    ]
});

Addressbook destination

Trigger a Workflow Template for an existing addressbook contact or group instead of a raw address. Passing a ContactID/GroupID does not create or update a contact, unlike the inline-address fields above.

const response = await client.Messaging.Workflow.SendMessage({
    WorkflowTemplateID: "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    Destinations: [
        { ContactID: "[Contact ID]" },
        { GroupID: "[Group ID]" }
    ]
});

Scheduled send

Delay the trigger to a specific SendTime.

const response = await client.Messaging.Workflow.SendMessage({
    WorkflowTemplateID: "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    ToNumber: "+64211111111",
    SendTime: "2026-09-01 09:00",
    Timezone: "New Zealand"
});

Response

SendMessage(...) resolves to a Promise of either a success or an error shape, never rejects for a normal validation or API failure, and always returns exactly one response object (no list-shaped fields).

FieldTypeDescription
Result"Success" | "Failed" | "Error" | "Unauthorized""Success" on a successful trigger. Client-side validation failures return "Error" before any HTTP call is made; "Failed" and "Unauthorized" come from the API itself.
MessageIDstringThe ID of the Workflow run you just triggered. Present on success.
JobNumstringJob number for the triggered run. Present on success.
StatusstringInitial status of the triggered run, e.g. "Queued". Present on success.
ErrorMessagestring[]Present on "Error"/"Failed"/"Unauthorized" results, describing what went wrong.

SMS

Send text messages to one or more destinations via the TNZ REST API, with optional Voice/RCS/WhatsApp fallback. SMS supports two-way messaging: track delivery status in real time (see Poll for status below), and receive replies from recipients (see Poll for inbound SMS and Poll for replies below, or the Webhooks section for a push-based alternative).

Message Body Tokens

Beyond the personalisation tokens in the Destination fields table below, your Message body supports these special inline tokens on the underlying TNZ REST API this SDK calls:

  • [[Link:https://example.com/page]]: automatically shortens the URL and tracks click-through engagement (URL Shortener).
  • [[File1]]: inserts a link to the first file passed via Attachments ([[File2]], [[File3]], etc. for additional attachments) (File Link).
  • [[REPLY]]: inserts a tappable link recipients can use to reply, even from devices without native SMS reply support (Reply Link).

Quick Example

import { TNZAPI } from 'tnzapi-ts';

const client = new TNZAPI({ AuthToken: "[Your Auth Token]" });

const response = await client.Messaging.SMS.SendMessage({
    Message: "Test SMS",
    ToNumber: "+64211111111",
    Mode: "Test"
});

if (response.Result === "Success") {
    console.log(`Success - MessageID: ${response.MessageID}`);
}

Parameters

ParameterTypeRequiredDescription
ReferencestringNoYour internal reference, returned in reports and webhooks.
MessagestringYes*Message body. Supports personalisation tokens [[FirstName]], [[Custom1]], etc. Max length 1000 characters.
TemplateIDstringYes*Pre-configured message template ID (alternative to Message).
DestinationsISMSDestination[]Yes†One or more destinations. See Destination fields below.
ToNumberstringYes†Single-recipient shorthand for Destinations: [{ ToNumber }]. Comma-separated values create multiple destinations.
GroupIDstringYes†Single-recipient shorthand for Destinations: [{ GroupID }]. Comma-separated values create multiple destinations.
ContactIDstringYes†Single-recipient shorthand for Destinations: [{ ContactID }]. Comma-separated values create multiple destinations.
SendTimestringNoSchedule delivery. Combine with Timezone.
TimezonestringNoWindows timezone name for SendTime (e.g. "New Zealand").
SubAccountstringNoSub-account code for billing separation.
DepartmentstringNoDepartment code.
ChargeCodestringNoCharge code.
MessageIDstringNoSupply your own message ID (otherwise auto-generated).
ReportTostringNoEmail address to receive delivery reports.
WebhookCallbackURLstringNoURL for delivery status callbacks.
WebhookCallbackFormatWebhookCallbackFormatYes‡Callback format: JSON / XML / POST / GET. Required if WebhookCallbackURL is set; otherwise the request is rejected with Result: "Error".
NotificationTypeNotificationTypeNoNotification delivery mode: None, Webhook, or Email.
Mode'Test'NoSet to "Test" to validate the request without sending. Any other non-empty value is rejected.
Attachmentsstring[]NoLocal file paths. Each file is read from disk and base64-encoded automatically; reference it in Message with [[File1]], [[File2]], etc. NZ carriers don't support inline MMS, so the recipient gets a link to the file instead. See the security note under Code samples below.
FallbackModeSMSFallbackMode | SMSFallbackMode[]NoFallback channel(s) if SMS fails, tried in the order given: None (default), RCS, WAPP (WhatsApp), or Voice. Pass an array to try more than one; it's joined into TNZ's comma-separated wire format automatically.
SMSEmailReplystringNoEmail address to receive SMS replies.
CharacterConversionbooleanNoConvert characters outside the GSM character set automatically. Default false.

*Either Message or TemplateID must be provided.
†Set via Destinations, via the ToNumber/GroupID/ContactID shorthand (these are additive with Destinations, not mutually exclusive), or via AddRecipient(...) on the builder API.
‡Only required when WebhookCallbackURL is set.

Two calling styles: pass every field above as a single object to SendMessage({ ... }), or accumulate destinations and attachments first via client.Messaging.SMS.AddRecipient(...)/AddAttachment(...) chained calls (each returns this) and finish with SendMessage({ Message: "..." }). Prefer the object-argument style with typed Destinations for anything beyond a single ad-hoc string recipient; the builder API is retained mainly for backward compatibility. Builder state is reset after each SendMessage(...) call. AddAttachment(...) checks the file exists when called - a missing path isn't silently dropped, it's recorded and surfaced as { Result: "Error", ErrorMessage: ["Attachment file not found: <path>"] } from the next SendMessage(...) call.

Destination fields (ISMSDestination)

FieldDescription
ToNumberDestination phone number, e.g. "+64211111111".
RecipientGeneric fallback field produced internally by the string form of the builder API - AddRecipient("+64211111111") sets { Recipient: "+64211111111" }. Same effect as ToNumber for SMS.
AttentionPersonalisation token [[Attention]].
FirstNamePersonalisation token [[FirstName]].
LastNamePersonalisation token [[LastName]].
CompanyPersonalisation token [[Company]].
Custom1Custom9Arbitrary per-recipient personalisation values, [[Custom1]] … [[Custom9]].
ContactIDAddressbook contact reference: sends to that contact instead of a raw number.
GroupIDAddressbook group reference: sends to all members of that group.
GroupCodeAlternative group lookup by code (instead of GroupID).

Code Samples

Single destination shorthand

The simplest way to send: pass a message and destination directly to SendMessage({ ... }).

const response = await client.Messaging.SMS.SendMessage({
    Message: "Office closed today.",
    ToNumber: "+64211111111",
    Mode: "Test"
});

Builder API with multiple recipients

Use AddRecipient(...) to accumulate several recipients before sending. Each call accepts a plain string, an ISMSDestination object, or an array of either.

const response = await client.Messaging.SMS
    .AddRecipient("+64211111111")
    .AddRecipient({ ToNumber: "+64222222222", FirstName: "Bob" })
    .SendMessage({
        Message: "Hi [[FirstName]]!",
        Mode: "Test"
    });

Addressbook ContactID/GroupID

Use the top-level GroupID/ContactID shorthand for a single group or contact, or add them as entries in Destinations to mix them with raw numbers in one send.

// Send to everyone in an addressbook group
const toGroup = await client.Messaging.SMS.SendMessage({
    Message: "Reminder: your subscription renews tomorrow.",
    GroupID: "4000000b-f002-4007-b00a-c00000000002",
    Mode: "Test"
});

// Send to a specific addressbook contact
const toContact = await client.Messaging.SMS.SendMessage({
    Message: "Hi there, your order has shipped!",
    ContactID: "8000000a-f002-4007-b00a-d00000000001",
    Mode: "Test"
});

// Mix addressbook references and raw numbers in one send via Destinations
const bulk = await client.Messaging.SMS.SendMessage({
    Message: "Reminder: your subscription renews tomorrow.",
    Destinations: [
        { GroupID: "4000000b-f002-4007-b00a-c00000000002" },
        { ContactID: "8000000a-f002-4007-b00a-d00000000001" },
        { ToNumber: "+64211111111" }
    ],
    Mode: "Test"
});

Bulk send with per-destination personalisation

Send one message to many destinations while personalising each copy with ISMSDestination fields.

const response = await client.Messaging.SMS.SendMessage({
    Message: "Hi [[FirstName]], your appointment is on [[Custom1]].",
    Destinations: [
        { ToNumber: "+64211111111", FirstName: "Alice", Custom1: "Monday 3pm" },
        { ToNumber: "+64222222222", FirstName: "Bob", Custom1: "Tuesday 10am" }
    ],
    Mode: "Test"
});

Scheduled send with webhook callback

Combine SendTime/Timezone to delay delivery with WebhookCallbackURL to get notified the moment it completes, instead of polling Reports.Status.

import { WebhookCallbackFormat } from 'tnzapi-ts';

const response = await client.Messaging.SMS.SendMessage({
    Message: "Your reminder.",
    ToNumber: "+64211111111",
    SendTime: "2026-09-01 09:00",
    Timezone: "New Zealand",
    WebhookCallbackURL: "https://yourapp.example.com/webhooks/sms",
    WebhookCallbackFormat: WebhookCallbackFormat.JSON,
    Mode: "Test"
});

Send a file via MessageLink

NZ carriers don't support MMS. Pass a local file path in Attachments - it's read from disk and base64-encoded automatically - then reference it in the message text with [[File1]]; the recipient gets an SMS with a link to the file instead of an inline attachment.

Security note: Attachments paths are read straight off local disk (fs.readFile) with no path allowlisting or sanitisation beyond an existence check. Don't pass a path built from unsanitised user input directly to Attachments/AddAttachment(...) - validate or resolve it against a known-safe directory first, the same as you would for any other server-side file read.

const response = await client.Messaging.SMS.SendMessage({
    Message: "Here's the photo you requested: [[File1]]",
    ToNumber: "+64211111111",
    Attachments: ["path/to/photo.jpg"],
    Mode: "Test"
});

Voice fallback

Set FallbackMode to retry delivery over another channel if SMS fails. Pass an array to try more than one, in order.

import { SMSFallbackMode } from 'tnzapi-ts';

const response = await client.Messaging.SMS.SendMessage({
    Message: "Critical alert: server down.",
    ToNumber: "+64211111111",
    FallbackMode: [SMSFallbackMode.Voice, SMSFallbackMode.WAPP],
    Mode: "Test"
});

Poll for status

Check delivery progress and per-recipient results any time after sending, via the Reports facade.

const status = await client.Reports.Status.Poll({
    MessageID: response.MessageID,
    Channel: "sms"
});

if (status.Result === "Success") {
    console.log(`JobStatus: ${status.JobStatus}`);
    for (const recipient of status.Recipients ?? []) {
        console.log(` -> ${recipient.Destination}: ${recipient.Status} (${recipient.Result})`);
    }
}

For the sms channel (the default when Channel is omitted), each entry in Recipients is actually an SMSReplyRecipientDTO at runtime, with a nested SMSReplies array - even though the field is statically typed as the plainer RecipientDTO[]. Cast if you need typed access to it here:

for (const recipient of status.Recipients ?? []) {
    const smsReplies = (recipient as any).SMSReplies ?? [];
    for (const reply of smsReplies) {
        console.log(`    reply: ${reply.MessageText}`);
    }
}

Poll for inbound SMS

Retrieve SMS replies received in the last TimePeriod minutes (or between DateFrom/DateTo), as an alternative to configuring a webhook.

const received = await client.Reports.SMSReceived.Poll({
    TimePeriod: 1440 // last 24 hours, in minutes (1-1440)
});

if (received.Result === "Success") {
    for (const message of received.Messages ?? []) {
        console.log(`From ${message.From}: ${message.MessageText}`);
    }
}

Poll for replies to a specific message

client.Reports.SMSReply.Poll(...) hits the same underlying data as Status.Poll({ Channel: "sms" }) above, but its Recipients are properly typed as SMSReplyRecipientDTO[], so recipient.SMSReplies is available without a cast.

const replies = await client.Reports.SMSReply.Poll({
    MessageID: response.MessageID
});

if (replies.Result === "Success") {
    for (const recipient of replies.Recipients ?? []) {
        for (const reply of recipient.SMSReplies ?? []) {
            console.log(`${recipient.Destination} replied: ${reply.MessageText}`);
        }
    }
}

Response

SendMessage(...) response

SendMessage(...) resolves to a MessagingApiSuccessResponseDTO on success or an ErrorResponseDTO on failure.

FieldTypeDescription
Resultstring"Success" on success; "Error", "Failed", or "Unauthorized" on failure.
ErrorMessagestring[]Present on the error variant only; human-readable error strings.
MessageIDstringThe ID of the message you just sent.
JobNumstringTNZ's internal job number for this send.
StatusstringInitial job status.

Reports.Status.Poll(...) / Reports.SMSReply.Poll(...) response

Both calls return the same field set. Status.Poll(...) also accepts a Channel parameter (other channels share this same endpoint); SMSReply.Poll(...) is SMS-only and always types its Recipients as SMSReplyRecipientDTO[].

FieldTypeDescription
ResultstringSee Getting Started.
MessageIDstringThe message this status is for.
JobStatusstringe.g. "Completed", "Processing", "Delayed".
JobNumstringTNZ's internal job number for this send.
AccountstringThe TNZ account that owns this job.
SubAccount / DepartmentstringEchoed from the original send.
ReferencestringEchoed from the Reference parameter.
CreatedTimeLocal / CreatedTimeUTCstringWhen the job was created, in local time and UTC.
DelayedTimeLocal / DelayedTimeUTCstringThe scheduled send time, if SendTime was set.
TimezonestringTimezone name used for scheduling.
CountnumberTotal recipients in the job.
CompletenumberRecipients processed so far.
Success / FailednumberRecipients successfully delivered / failed.
PricenumberJob total cost.
TotalRecords / RecordsPerPage / PageCount / PagenumberPagination metadata for Recipients, controlled by the call's RecordsPerPage/Page parameters (default 100 per page, max 999; page default 1).
RecipientsRecipientDTO[] / SMSReplyRecipientDTO[]Per-recipient results. See table below.
Recipient object (each entry in Recipients)
FieldTypeDescription
TypestringRecipient channel type.
DestSeqnumberTNZ's internal sequence ID for this recipient within the job.
DestinationstringThe recipient's phone number.
ContactIDstringAddressbook contact reference, if sent via ContactID/GroupID.
StatusstringDelivery status for this recipient.
ResultstringHuman-readable delivery result for this recipient.
SentTimeLocal / SentTimeUTCstringWhen the message was actually sent to this recipient.
Attention / Company / Custom1Custom9stringEchoed personalisation fields. See Destination fields above.
RemoteIDstringCarrier/network-assigned identifier for this delivery, if available.
PricenumberPer-recipient cost.
SMSRepliesSMSReplyRecipientSMSReplyDTO[]Inbound replies from this recipient. Only present on SMSReplyRecipientDTO entries - always for SMSReply.Poll(...), and for Status.Poll(...) when Channel is "sms" (the default). See table below.
SMSReplies object (each entry in Recipients[].SMSReplies)
FieldTypeDescription
ReceivedIDstringUnique identifier for this reply.
ReceivedTimeLocal / ReceivedTimeUTCstringWhen the reply was received.
TimezonestringTimezone name for ReceivedTimeLocal.
FromstringThe replying phone number.
MessageTextstringThe reply body.

Reports.SMSReceived.Poll(...) response

FieldTypeDescription
ResultstringSee Getting Started.
TotalRecords / RecordsPerPage / PageCount / PagenumberPagination metadata for Messages, controlled by the call's RecordsPerPage/Page parameters.
MessagesSMSReceivedDTO[]Inbound SMS messages. See table below.
Message object (each entry in Messages)
FieldDescription
ReceivedIDUnique identifier for this message.
MessageIDThe original outbound message this replies to, if determinable.
JobNumThe original send job's number, if applicable.
SubAccount / DepartmentEchoed billing codes from the original send.
ReceivedTimeLocal / ReceivedTimeUTCWhen the message was received, in local time and UTC.
FromThe sender's phone number.
ContactIDAddressbook contact reference, if the sender matched one.
MessageTextThe message body.
TimezoneTimezone name for ReceivedTimeLocal.

Email

TNZ's Email API sends plain-text or HTML emails. Provide your own HTML, or specify a TemplateID so your team can manage the design in the Dashboard's WYSIWYG editor without needing code changes. Attach files, and TNZ automatically tracks link clicks in the message body. Delivery status is available for every email you send, including invoices, newsletters, and alerts.

Quick Example

import { TNZAPI } from 'tnzapi-ts';

const client = new TNZAPI({ AuthToken: '[Your Auth Token]' });

const response = await client.Messaging.Email.SendMessage({
    FromEmail: 'from@test.com',        // Optional - leave blank to use your API username as sender
    EmailSubject: 'Test Email',
    MessagePlain: 'Test Email Body',
    EmailAddress: 'email.one@test.com',
    Mode: 'Test',                      // Test mode
});

if (response.Result === 'Success') {
    console.log(`Success - MessageID: ${response.MessageID}`);
}

Parameters

ParameterTypeRequiredDescription
EmailSubjectstringYesSubject line for the email.
MessagePlainstringYes*Plain-text body.
MessageHTMLstringYes*HTML body. Can be combined with MessagePlain for a multipart email.
TemplateIDstringYes*Pre-configured message template ID (alternative to MessagePlain/MessageHTML).
DestinationsIEmailDestination[]Yes†One or more destinations, as plain object literals. See Destination fields below.
EmailAddressstringYes†Single-destination shorthand. Comma-separate multiple addresses, e.g. "a@test.com,b@test.com".
GroupIDstringYes†Single addressbook group shorthand (alternative/addition to Destinations).
ContactIDstringYes†Single addressbook contact shorthand (alternative/addition to Destinations).
FromEmailstringNoSender address. Leave blank to use your API username.
FromstringNoLegacy alternate sender field. Prefer FromEmail.
SMTPFromstringNoLegacy alternate sender field, rarely needed. Prefer FromEmail.
ReplyTostringNoReply-To address: replies from the recipient are sent here instead of FromEmail.
CCEmailstringNoTracked CC address added to the email (chargeable, per recipient).
BCCEmailstringNoUntracked BCC address added to the email (chargeable, per recipient).
Attachmentsstring[]NoLocal file paths, read and base64-encoded automatically. See the security note in Code Samples below.
ReferencestringNoYour internal reference, returned in reports and webhooks.
MessageIDstringNoSupply your own message ID (otherwise auto-generated).
SendTimestringNoSchedule delivery. Combine with Timezone.
TimezonestringNoWindows timezone name for SendTime, e.g. "New Zealand".
SubAccountstringNoSub-account code for billing separation.
DepartmentstringNoDepartment code.
ChargeCodestringNoCharge code for billing separation.
ReportTostringNoEmail address to receive delivery reports.
NotificationTypeNotificationTypeNoNotification delivery mode: None, Webhook, or Email.
WebhookCallbackURLstringNoURL for delivery status callbacks.
WebhookCallbackFormatWebhookCallbackFormatYes‡Callback format: JSON, XML, POST, or GET.
Mode'Test'NoSet to 'Test' to validate without sending. Leave unset for a live send.

*At least one of MessagePlain, MessageHTML, or TemplateID must be provided; MessagePlain and MessageHTML may be combined to send a multipart email.
†Set via Destinations, EmailAddress, GroupID, or ContactID, or via AddRecipient(...) chained on the builder.
‡Only required when WebhookCallbackURL is set; otherwise the request is rejected with Result: "Error".

Two calling styles: every field above can be passed directly in the object argument to SendMessage({ ... }) in one call, or accumulated first via client.Messaging.Email.AddRecipient(...)/AddAttachment(...) chained calls and finished with SendMessage({ ... }) carrying the remaining fields (or no arguments at all, if everything was set on the builder). SendMessage(...) always returns a Promise; validation failures resolve to Result: "Error" rather than throwing.

Destination fields (IEmailDestination)

Email has no shared destination class - a destination is a plain object literal matching the IEmailDestination shape. Its primary field is EmailAddress; Recipient has the same effect and is what AddRecipient("email.one@test.com") sets when given a bare string.

FieldDescription
EmailAddressDestination email address, e.g. "email.one@test.com".
RecipientGeneric destination address, sent as-is regardless of channel (same effect as EmailAddress here). Set automatically when AddRecipient(...) is given a bare string.
FirstName / LastName / Company / AttentionPersonalisation tokens, e.g. [[FirstName]].
Custom1Custom9Arbitrary per-recipient personalisation values, [[Custom1]] … [[Custom9]].
ContactIDAddressbook contact reference: sends to that contact instead of a raw address.
GroupIDAddressbook group reference: sends to all members of that group.
GroupCodeAlternative group lookup by code (instead of GroupID).

Code Samples

Single destination, via the builder

AddRecipient(...) accepts a bare string, an object literal, or an array of either, and can be chained before a final SendMessage({ ... }) carrying the remaining fields.

const response = await client.Messaging.Email
    .AddRecipient('email.one@test.com')
    .SendMessage({
        FromEmail: 'from@test.com',
        EmailSubject: 'Test Email',
        MessagePlain: 'Test Email Body',
        Mode: 'Test', // Test mode
    });

HTML email

Email clients have inconsistent CSS support, so inline styles (rather than a <style> block) are the safest way to style an HTML body:

const htmlBody = `
    <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
        <h2 style="color: #1057fc;">Your order has shipped!</h2>
        <p>Hi [[FirstName]],</p>
        <p>Great news! Your order <strong>#12345</strong> is on its way and should arrive within 2-3 business days.</p>
        <p style="text-align: center; margin: 24px 0;">
            <a href="https://example.com/track/12345" style="background-color: #1057fc; color: #ffffff; padding: 12px 24px; border-radius: 4px; text-decoration: none;">Track your order</a>
        </p>
        <p style="color: #787878; font-size: 12px;">If you have any questions, reply to this email and we'll be happy to help.</p>
    </div>
`;

const response = await client.Messaging.Email.SendMessage({
    EmailSubject: 'Your order has shipped',
    MessageHTML: htmlBody,
    Destinations: [
        { EmailAddress: 'email.one@test.com', FirstName: 'Alice' },
    ],
    Mode: 'Test', // Test mode
});

With an attachment

A local file path in Attachments is read via fs.readFile and base64-encoded automatically - no manual base64 handling needed.

Security note: each entry in Attachments: string[] is read directly from the local filesystem with no path validation, allow-list, or traversal check. Never let untrusted or user-controlled input reach Attachments directly - doing so risks arbitrary file exfiltration from the server. The builder's AddAttachment(path) at least checks the file exists up front, returning a friendly Attachment file not found: <path> error from the next SendMessage(...) call if it's missing; the plain Attachments: [...] array in the object argument bypasses that early check entirely.

const response = await client.Messaging.Email
    .AddRecipient('email.one@test.com')
    .AddAttachment('path/to/doc.pdf')
    .SendMessage({
        EmailSubject: 'Test Email',
        MessagePlain: 'See attached.',
        Mode: 'Test', // Test mode
    });

Custom sender, reply-to, and CC

Override the sender identity and add a CC and Reply-To address.

const response = await client.Messaging.Email.SendMessage({
    EmailSubject: 'Your invoice is ready',
    MessagePlain: 'Please find your invoice attached.',
    From: 'Test Company Billing',
    FromEmail: 'billing@test.com',
    ReplyTo: 'accounts@test.com',
    CCEmail: 'manager@test.com',
    EmailAddress: 'email.one@test.com',
    Mode: 'Test', // Test mode
});

Multiple recipients, via Destinations

Pass an array of destination object literals directly in Destinations for per-recipient personalisation in a single call.

const response = await client.Messaging.Email.SendMessage({
    EmailSubject: 'Test Email',
    MessagePlain: 'Hi [[FirstName]], your appointment is on [[Custom1]].',
    Destinations: [
        { EmailAddress: 'email.one@test.com', FirstName: 'Alice', Custom1: 'Monday 3pm' },
        { EmailAddress: 'email.two@test.com', FirstName: 'Bob', Custom1: 'Tuesday 10am' },
    ],
    Mode: 'Test', // Test mode
});

Addressbook shorthand

GroupID and ContactID send to an existing Addressbook group or contact without building a Destinations array.

const response = await client.Messaging.Email.SendMessage({
    EmailSubject: 'Test Email',
    MessagePlain: 'Test Email Body',
    GroupID: 'GGGGGGGG-BBBB-BBBB-CCCC-DDDDDDDDDDDD',
    Mode: 'Test', // Test mode
});

Response

SendMessage(...) response

FieldTypeDescription
ResultstringSee Getting Started.
ErrorMessagestring[]Present only when Result is not "Success". See Getting Started.
MessageIDstringThe ID of the message you just sent.
JobNumstringTNZ's internal job number for this send.
StatusstringSee Getting Started's Common Response Enums.

TTS (Text-to-speech)

Convert written text into a spoken voice call. Used for automated alerts, appointment reminders, or customer surveys. Route callers or capture responses with interactive keypad menus, and rely on built-in answering machine detection (AnswerPhoneMode) to play an alternate message when a voicemail picks up instead of a person. TTS is driven entirely by spoken-text fields (MessageToPeople, MessageToAnswerPhones, and the various CallRouteMessage* fields); it has no audio-file attachment fields. AddAttachment(...) exists on the shared builder base class and is technically callable on client.Messaging.TTS, but ITTSArgs declares no Attachments field and there's no documented support for playing a file on a TTS call - use Voice instead if you need to play a pre-recorded audio file.

Quick Example

import { TNZAPI } from 'tnzapi-ts';

const client = new TNZAPI({ AuthToken: "[Your Auth Token]" });

const response = await client.Messaging.TTS.SendMessage({
    MessageToPeople: "Hello, this is a call from test. This is relevant information.",
    ToNumber: "+64211111111",
    Mode: "Test" // Test mode
});

console.log(response.MessageID);

Parameters

ParameterTypeRequiredDescription
MessageToPeoplestringYes*Message read aloud when a live person answers. Supports personalisation tokens such as [[FirstName]].
TemplateIDstringYes*Pre-configured message template ID (alternative to MessageToPeople).
DestinationsITTSDestination[]Yes†One or more destinations. See Destination fields below.
ToNumberstringYes†Single-destination shorthand, e.g. "+64211111111". Comma-separate for multiple. Resolves internally to a MainPhone destination.
GroupIDstringYes†Single addressbook group to call (alternative/addition to Destinations). Comma-separated for multiple.
ContactIDstringYes†Single addressbook contact to call (alternative/addition to Destinations). Comma-separated for multiple.
ReferencestringNoYour internal reference, echoed back in reports and webhooks.
MessageToAnswerPhonesstringNoMessage read when an answering machine is detected.
AnswerPhoneModeAnswerPhoneModeNoHow to handle an answering machine: "NDAS", "NDAF", "DAS", or "DAF". Default "NDAS". See AnswerPhoneMode values below.
KeypadsITTSKeypad[]NoKeypad menu options. See Keypad fields below.
KeypadOptionRequiredbooleanNoRequire the caller to press a key before the call proceeds. Default false.
CallRouteMessageOnWrongKeystringNoMessage played if an invalid key is pressed.
CallRouteMessageToPeoplestringNoMessage played before connecting the caller to an operator.
CallRouteMessageToOperatorsstringNoMessage played to the operator receiving the routed call.
NumberOfOperatorsnumberNoLive operators available for keypad-routed calls. The SDK sends 0 when left unset; there's no client-side minimum enforced.
RetryAttemptsnumberNoRetry attempts on no-answer/busy. Maximum 5.
RetryPeriodnumberNoMinutes between retry attempts. Maximum 60.
CallerIDstringNoCaller ID shown to the recipient.
VoiceTTSVoiceNoSynthesised voice: "Female1", "Male1", "Nicole", "Russell", "Amy", "Brian", or "Emma". Default "Female1".
EndCallMessagestringNoMessage played at the end of the call, after all other messages.
OptionsstringNoAdvanced voice options (survey recording, DTMF capture, etc). Contact TNZ for supported values.
SendTimestringNoSchedule delivery. Combine with Timezone.
TimezonestringNoWindows timezone name for SendTime, e.g. "New Zealand", "AUS Eastern".
SubAccountstringNoSub-account code for billing separation.
DepartmentstringNoDepartment code.
ChargeCodestringNoCharge code for billing.
MessageIDstringNoSupply your own message ID (otherwise auto-generated).
ReportTostringNoEmail address to receive delivery reports.
WebhookCallbackURLstringNoURL for delivery status callbacks.
WebhookCallbackFormatWebhookCallbackFormatYes‡"JSON", "XML", "POST", or "GET".
NotificationTypeNotificationTypeNo"None", "Webhook", or "Email".
Mode'Test'NoSet to "Test" to validate without sending.

*Either MessageToPeople or TemplateID must be provided.
†Provide at least one destination via ToNumber (single-value shorthand, comma-separated for multiple), Destinations (an array), or GroupID/ContactID. ToNumber here is request-level shorthand, resolving internally to one or more Destinations: [{ MainPhone: ... }] entries; see Destination fields below for the fuller per-item field set available inside Destinations.
‡Only required when WebhookCallbackURL is set; otherwise the request is rejected with Result: "Error".

Object arguments and the builder: client.Messaging.TTS.SendMessage({...}) accepts every field in the table above directly on the object passed in - this is the primary, and simplest, way to send a TTS call. A secondary, chainable builder is also available: AddRecipient(...) queues one or more destinations (a bare string, an ITTSDestination object, or an array of either), and AddKeypad(...) queues one keypad entry at a time. Both return this so calls chain, and whatever's queued is merged with the fields passed to the final, awaited SendMessage({...}) call; state queued on the builder is cleared once that call runs. Prefer the object-argument form when you already have a typed Destinations/Keypads array to hand; the builder is most useful for adding a recipient or keypad entry one at a time. There's no .Set(...) method and no plural AddKeypads(...)/AddDestinations(...) helpers - call AddKeypad(...) once per entry, and pass an array to AddRecipient(...) for more than one destination at a time.

Keypad fields (Keypads list items)

Built one at a time via AddKeypad(tone, routeNumber, play?, playSection?) - positional arguments, in that order, not keyword arguments - or supplied directly as an array of objects in Keypads. Pass an empty string for routeNumber in the builder form when an entry only plays a message and doesn't route.

FieldTypeDescription
TonenumberThe DTMF digit this entry responds to (0-9).
RouteNumberstringPhone number to route the call to when this key is pressed.
PlaystringMessage read aloud when this key is pressed, instead of or as well as routing.
PlaySectionstringWhere in the call flow this keypad applies: "Main" (the main MessageToPeople), "AnswerPhone" (plays MessageToAnswerPhones), or "WrongKey" (plays CallRouteMessageOnWrongKey).

Destination fields (ITTSDestination)

TTS destinations are plain objects matching the ITTSDestination interface - there's no shared Destination class across channels in this library; each channel has its own destination interface, and TTS's has no MobilePhone/EmailAddress/FaxNumber fields to ignore. TTS's primary destination field is MainPhone, set by the top-level ToNumber shorthand on SendMessage(...). The bare-string form of AddRecipient("+64211111111") instead sets Recipient, a generic fallback field read the same way as MainPhone.

FieldDescription
MainPhoneDestination phone number, e.g. "+64211111111". TTS's primary destination field; set by the top-level ToNumber shorthand.
RecipientGeneric fallback field, set by the bare-string form of AddRecipient("+64211111111"). Read the same as MainPhone.
ContactIDAddressbook contact reference. Sends to that contact instead of a raw number.
GroupIDAddressbook group reference. Sends to all members of that group.
GroupCodeAlternative group lookup by code (instead of GroupID).
FirstName / LastName / Company / AttentionPersonalisation tokens, e.g. [[FirstName]].
Custom1-Custom9Arbitrary per-recipient personalisation values, [[Custom1]] ... [[Custom9]].

Code Samples

Single destination shorthand

The simplest way to send: pass the spoken text and destination directly to SendMessage(...).

const response = await client.Messaging.TTS.SendMessage({
    MessageToPeople: "Hello, this is a call from test. This is relevant information.",
    ToNumber: "+64211111111",
    Mode: "Test" // Test mode
});

With a keypad menu, via the builder

Add interactive keypad options so callers can route themselves to a different number by pressing a key. AddRecipient(...) and AddKeypad(...) queue state on client.Messaging.TTS; the final, awaited SendMessage({...}) call sends it and clears that state.

const response = await client.Messaging.TTS
    .AddRecipient("+64211111111")
    .AddKeypad(1, "+64211112222", "Connecting you to sales now.")
    .AddKeypad(2, "+64211113333", "Connecting you to support now.")
    .SendMessage({
        MessageToPeople: "Press 1 for sales, press 2 for support.",
        Mode: "Test" // Test mode
    });

Multi-option keypad menu

Offer several keys at once: route two of them to different numbers, play a short message on another without routing, and replay the original message on a fourth via PlaySection. RouteNumber/Play/PlaySection are all optional on each object in Keypads, so only the fields each entry needs have to be included.

const response = await client.Messaging.TTS.SendMessage({
    MessageToPeople: "Press 1 for sales, press 2 for support, press 3 to hear our address, or press 9 to hear this message again.",
    KeypadOptionRequired: true,
    CallRouteMessageOnWrongKey: "Sorry, that key isn't recognised. Please try again.",
    ToNumber: "+64211111111",
    Keypads: [
        { Tone: 1, RouteNumber: "+64211112222" },
        { Tone: 2, RouteNumber: "+64211113333" },
        { Tone: 3, Play: "We're located at 123 Example Street, Auckland." },
        { Tone: 9, PlaySection: "Main" }
    ],
    Mode: "Test" // Test mode
});

Multiple destinations

Call more than one number in a single request by passing several entries in Destinations.

const response = await client.Messaging.TTS.SendMessage({
    MessageToPeople: "Hello, this is a call from test.",
    Destinations: [
        { MainPhone: "+64211111111" },
        { MainPhone: "+64222222222" }
    ],
    Mode: "Test" // Test mode
});

With personalisation

Set personalisation fields on each destination for use with [[FirstName]]-style tokens in MessageToPeople.

const response = await client.Messaging.TTS.SendMessage({
    MessageToPeople: "Hello [[FirstName]], this is a reminder about your appointment on [[Custom1]].",
    Destinations: [
        { MainPhone: "+64211111111", FirstName: "Alice", Custom1: "Monday 3pm" },
        { MainPhone: "+64222222222", FirstName: "Bob", Custom1: "Tuesday 10am" }
    ],
    Mode: "Test" // Test mode
});

Addressbook group and contact shorthand

GroupID and ContactID work the same way as ToNumber: pass them directly on the top-level args instead of wrapping them in Destinations.

// Call everyone in an addressbook group
const toGroup = await client.Messaging.TTS.SendMessage({
    MessageToPeople: "Staff meeting at 2pm today in the main boardroom.",
    GroupID: "4000000b-f002-4007-b00a-c00000000002",
    Mode: "Test" // Test mode
});

// Call a specific addressbook contact
const toContact = await client.Messaging.TTS.SendMessage({
    MessageToPeople: "Hello, this is a reminder about your appointment tomorrow at 9am.",
    ContactID: "8000000a-f002-4007-b00a-d00000000001",
    Mode: "Test" // Test mode
});

Scheduled send

Delay the call to a specific SendTime. Timezone takes a Windows timezone name, not an IANA name.

const response = await client.Messaging.TTS.SendMessage({
    MessageToPeople: "Your reminder call.",
    ToNumber: "+64211111111",
    SendTime: "2026-09-01 09:00",
    Timezone: "New Zealand",
    Mode: "Test" // Test mode
});

Retry attempts, caller ID, and voice

Configure automatic retries on no-answer or busy, set the caller ID shown to recipients, choose a voice, and email a report once the job completes.

import { TTSVoice } from 'tnzapi-ts';

const response = await client.Messaging.TTS.SendMessage({
    MessageToPeople: "Hello, this is a call from test. This is relevant information.",
    ToNumber: "+64211111111",
    CallerID: "+6499999999",
    Voice: TTSVoice.Emma,
    RetryAttempts: 3,
    RetryPeriod: 1,
    ReportTo: "report@example.com",
    Mode: "Test" // Test mode
});

AnswerPhoneMode values

ValueBehaviour
NDASNo detect, always speak - treats every answer as a live person. Default.
NDAFNo detect, always fax.
DASDetect and speak - plays MessageToAnswerPhones when an answering machine is detected.
DAFDetect and fax.

Actions and Status

Unlike some other TNZ SDKs, client.Messaging.TTS doesn't expose Reschedule, Abort, Resubmit, or Pacing methods directly. Call these through the cross-channel client.Actions.<Verb>.SendRequest({ Channel: "tts", MessageID, ... }) methods documented under Actions, passing Channel: "tts". TTS supports all four actions, including Pacing for adjusting NumberOfOperators on an in-progress keypad-routed job. Status polling works the same way, via client.Reports.Status.Poll({ MessageID, Channel: "tts" }); see Reports.

Adjust pacing

const response = await client.Actions.Pacing.SendRequest({
    MessageID: "ID-abc123",
    Channel: "tts",
    NumberOfOperators: 1
});

Poll for status

Check call progress and per-recipient results any time after sending. Unlike some other TNZ SDKs, Recipients entries here are typed objects, accessed with recipient.Field, not plain dictionaries.

const status = await client.Reports.Status.Poll({
    MessageID: "ID-abc123",
    Channel: "tts"
});

if (status.Result === "Success") {
    console.log(`JobStatus: ${status.JobStatus}, JobNum: ${status.JobNum}`);

    for (const recipient of status.Recipients) {
        console.log(`${recipient.Destination}: ${recipient.Status} (${recipient.Result})`);
    }
}

Response

SendMessage(...) response

Check response.Result === "Success" before reading the other fields.

FieldTypeDescription
Resultstring"Success" on success; "Failed", "Error", or "Unauthorized" otherwise.
MessageIDstringThe ID of the call you just placed. Present on success only.
JobNumstringTNZ's internal job number for this send. Present on success only.
StatusstringThe job's initial status, e.g. "Queued". Present on success only.
ErrorMessagestring[]Present on failure only; explains what went wrong.

Voice

Voice sends pre-recorded audio calls: a WAV or MP3 file is played to the person (or answering machine) that picks up. This suits alerts where a specific recorded voice matters, or where the message was already recorded for another purpose. Manage calls to individuals or large groups, with retry attempts for failed calls (RetryAttempts/RetryPeriod), answering machine detection, and interactive keypad responses.

Voice and TTS share the same Keypad wire shape (Tone, RouteNumber, Play, PlaySection) and the same AnswerPhoneMode values, but IVoiceKeypad adds a File/PlayFile pair that TTS's keypad interface doesn't have, for playing a local audio clip on a key press. Voice also has no Voice field (TTS's voice-selection parameter): it isn't in IVoiceArgs, since Voice plays back a recording rather than synthesising speech. MessageToPeople, MessageToAnswerPhones, and the three CallRouteMessage* fields remain plain string fields either way: set directly, they're treated the same way TTS treats them (spoken text, or a base64 string you've already encoded yourself); set via VoiceFiles, the SDK reads a local WAV/MP3 file, base64-encodes it, and substitutes that data into the named field before the request is sent.

Quick Example

import { TNZAPI } from 'tnzapi-ts';

const client = new TNZAPI({ AuthToken: '[Your Auth Token]' });

const response = await client.Messaging.Voice.SendMessage({
    ToNumber: '+64211111111',
    VoiceFiles: [
        { Name: 'MessageToPeople', File: 'path/to/message.wav' },
    ],
    Mode: 'Test', // Test mode
});

if (response.Result === 'Success') {
    console.log(`Success - MessageID: ${response.MessageID}`);
}

Parameters

ParameterTypeRequiredDescription
TemplateIDstringYes*Pre-configured audio template ID, typically built in the Dashboard.
VoiceFilesIVoiceFile[]Yes*Local audio files to play, mapped onto the named fields below. See the security note in Code samples.
MessageToPeoplestringYes*Played to a person answering the call: spoken text, a pre-encoded base64 WAV/MP3 string, or (via VoiceFiles) audio from a local file.
MessageToAnswerPhonesstringNoPlayed if an answering machine picks up instead of a person. Same accepted forms as MessageToPeople.
DestinationsIVoiceDestination[]Yes†One or more destinations, as plain object literals. See Destination fields below.
ToNumberstringYes†Single-destination shorthand, resolves to MainPhone. Comma-separate multiple numbers, e.g. "+64211111111,+64222222222".
GroupIDstringYes†Single addressbook group shorthand (alternative/addition to Destinations); comma-separated for multiple.
ContactIDstringYes†Single addressbook contact shorthand (alternative/addition to Destinations); comma-separated for multiple.
AnswerPhoneModeAnswerPhoneModeNoHow to handle an answering machine: NDAS, NDAF, DAS, or DAF.
CallerIDstringNoCaller ID shown to the recipient (must be whitelisted under your account).
RetryAttemptsnumberNoNumber of retry attempts on no-answer/busy.
RetryPeriodnumberNoMinutes between retry attempts.
NumberOfOperatorsnumberNoNumber of simultaneous operators for keypad-routed calls.
KeypadOptionRequiredbooleanNoForce the caller to press a key before the call proceeds.
KeypadsIVoiceKeypad[]NoKeypad menu options. See Keypad fields below.
CallRouteMessageToPeoplestringNoPlayed before routing the call to an operator. Same accepted forms as MessageToPeople.
CallRouteMessageToOperatorsstringNoPlayed to the operator receiving the routed call. Same accepted forms as MessageToPeople.
CallRouteMessageOnWrongKeystringNoPlayed if an invalid key is pressed. Same accepted forms as MessageToPeople.
EndCallMessagestringNoPlayed at the end of the call, after all other messages. Unlike the fields above, there's no VoiceFiles convenience for this one: supply spoken text, or a base64 WAV/MP3 string you've encoded yourself.
OptionsstringNoAdvanced call options (survey/DTMF-capture style features). Contact TNZ for supported values.
ReferencestringNoYour internal reference, returned in reports and webhooks.
MessageIDstringNoSupply your own message ID (otherwise auto-generated).
SendTimestringNoSchedule delivery. Combine with Timezone.
TimezonestringNoWindows timezone name for SendTime, e.g. "New Zealand".
SubAccountstringNoSub-account code for billing separation.
DepartmentstringNoDepartment code.
ChargeCodestringNoCharge code for billing separation.
ReportTostringNoEmail address to receive delivery reports.
NotificationTypeNotificationTypeNoNotification delivery mode: None, Webhook, or Email.
WebhookCallbackURLstringNoURL for delivery status callbacks.
WebhookCallbackFormatWebhookCallbackFormatYes‡Callback format: JSON, XML, POST, or GET.
Mode'Test'NoSet to 'Test' to validate without sending. Leave unset for a live send.

*At least one of TemplateID, MessageToPeople, or VoiceFiles must be provided. If a VoiceFiles entry names the same field as a MessageToPeople/MessageToAnswerPhones/CallRouteMessage* string set directly in the same request, the VoiceFiles audio takes priority and overwrites it.
†Provide at least one destination via Destinations, ToNumber (single-destination shorthand, resolves to MainPhone), GroupID, or ContactID. The three shorthand fields combine additively with Destinations and with each other.
‡Only required when WebhookCallbackURL is set; otherwise the request is rejected with Result: "Error".

Two calling styles: every field above can be passed directly in the object argument to SendMessage({ ... }) in one call, or accumulated first via client.Messaging.Voice.AddRecipient(...)/AddVoiceFile(...)/AddKeypad(...) chained calls and finished with SendMessage({ ... }) carrying the remaining fields (or no arguments at all, if everything was set on the builder). SendMessage(...) always returns a Promise; validation failures resolve to Result: "Error" rather than throwing.

Keypad fields (IVoiceKeypad)

Built via AddKeypad(tone, routeNumber, file?, playSection?), called once per key and chained; or supplied directly as object literals in Keypads. At least one of RouteNumber, Play, PlayFile, File, or PlaySection must be set per entry.

FieldTypeDescription
TonenumberThe DTMF digit this entry responds to (0–9). Required.
RouteNumberstringPhone number to route the call to when this key is pressed.
FilestringLocal audio file path: the third argument to AddKeypad(...). Read and base64-encoded automatically into PlayFile before the request is sent - it never reaches the wire itself. See the security note in Code samples below.
PlayFilestringBase64-encoded audio played when this key is pressed. Populated automatically from File; can also be supplied directly as a pre-encoded base64 string.
PlaystringSpoken text played when this key is pressed, for a text fallback instead of audio - same accepted forms as the top-level MessageToPeople. Not set by AddKeypad(...); only reachable by supplying Keypads object literals directly.
PlaySectionstringWhere in the call flow this keypad applies: "Main" (the main MessageToPeople), "AnswerPhone" (plays during MessageToAnswerPhones), or "WrongKey" (plays CallRouteMessageOnWrongKey).

Destination fields (IVoiceDestination)

Voice has no shared destination class - a destination is a plain object literal matching the IVoiceDestination shape. Unlike some other TNZ SDKs, this interface carries no MobilePhone, EmailAddress, or FaxNumber field at all: only fields relevant to a phone call are present. Its primary field is MainPhone; Recipient has the same effect and is what AddRecipient("+64211111111") sets when given a bare string.

FieldDescription
MainPhoneDestination phone number, e.g. "+64211111111". Voice's primary destination field; the top-level ToNumber shorthand resolves here.
RecipientGeneric destination address, sent as-is regardless of channel. Set automatically when AddRecipient(...) is given a bare string; same effect as MainPhone here.
FirstName / LastName / Company / AttentionPersonalisation tokens, e.g. [[FirstName]].
Custom1Custom9Arbitrary per-recipient personalisation values, [[Custom1]] … [[Custom9]].
ContactIDAddressbook contact reference: sends to that contact instead of a raw number.
GroupIDAddressbook group reference: sends to all members of that group.
GroupCodeAlternative group lookup by code (instead of GroupID).

Code Samples

Single destination shorthand

The simplest way to send: pass a destination directly to SendMessage(...). The pre-recorded audio comes from TemplateID, typically built in the Dashboard.

const response = await client.Messaging.Voice.SendMessage({
    ToNumber: '+64211111111',
    TemplateID: '[Your Template ID]',
    Mode: 'Test', // Test mode
});

Via the builder, with a keypad menu

AddVoiceFile(field, file) takes the target field name and a local file path as two plain arguments (not an object). AddKeypad(tone, routeNumber, file, playSection) takes the file path as its third positional argument.

Security note: both VoiceFiles[].File and Keypads[].File are read directly from the local filesystem with no path validation, allow-list, or traversal check. Never let untrusted or user-controlled input reach either field directly - doing so risks arbitrary file exfiltration from the server. AddVoiceFile(...) at least checks the file exists up front, returning a friendly Attachment file not found: <path> error from the next SendMessage(...) call if it's missing; AddKeypad(...) does not perform that check, nor does a plain VoiceFiles: [...]/Keypads: [...] array passed straight to the object argument - a missing keypad audio file only surfaces as a generic Failed to process attachments: ... error once SendMessage(...) actually tries to read it.

const response = await client.Messaging.Voice
    .AddRecipient('+64211111111')
    .AddVoiceFile('MessageToPeople', 'path/to/audio.wav')
    .AddKeypad(1, '+64211112222', '', 'Main')
    .SendMessage({
        Mode: 'Test', // Test mode
    });

Multiple audio files with keypad routing

Set separate audio for the live-answer message and the answering-machine fallback, and add a keypad menu where key 1 routes to another number and key 2 plays its own audio clip.

import { AnswerPhoneMode } from 'tnzapi-ts';

const response = await client.Messaging.Voice.SendMessage({
    Destinations: [
        { MainPhone: '+64211111111' },
    ],
    VoiceFiles: [
        { Name: 'MessageToPeople', File: 'path/to/audio.wav' },
        { Name: 'MessageToAnswerPhones', File: 'path/to/voicemail.wav' },
    ],
    AnswerPhoneMode: AnswerPhoneMode.DAS,
    KeypadOptionRequired: true,
    NumberOfOperators: 2,
    Keypads: [
        { Tone: 1, RouteNumber: '+64211112222' },
        { Tone: 2, File: 'path/to/opening-hours.wav' },
    ],
    Mode: 'Test', // Test mode
});

Multiple destinations

Call more than one number in a single request by passing several entries in Destinations.

const response = await client.Messaging.Voice.SendMessage({
    TemplateID: '[Your Template ID]',
    Destinations: [
        { MainPhone: '+64211111111' },
        { MainPhone: '+64222222222' },
    ],
    Mode: 'Test', // Test mode
});

With personalisation

Destination personalisation fields are always echoed back in the recipient's status result, but they can only be spoken when the call relies on MessageToPeople as plain text (the same TTS-style handling TTS gives it) - a fixed VoiceFiles recording can't substitute merge tags into already-recorded audio.

const response = await client.Messaging.Voice.SendMessage({
    MessageToPeople: 'Hello [[FirstName]], this is a reminder about your appointment.',
    Destinations: [
        { MainPhone: '+64211111111', FirstName: 'Alice', Custom1: 'Account #4432' },
        { MainPhone: '+64222222222', FirstName: 'Bob', Custom1: 'Account #7788' },
    ],
    Mode: 'Test', // Test mode
});

Scheduled send

Delay the call to a specific SendTime.

const response = await client.Messaging.Voice.SendMessage({
    ToNumber: '+64211111111',
    TemplateID: '[Your Template ID]',
    SendTime: '2026-09-01 09:00',
    Timezone: 'New Zealand',
    Mode: 'Test', // Test mode
});

Voice jobs also support the platform-wide Actions (Abort, Reschedule, Resubmit, Pacing) once submitted, via client.Actions.<Verb>.SendRequest({ MessageID: response.MessageID, Channel: 'voice', ... }). Resubmit and Pacing both accept Channel: 'voice'; Pacing's NumberOfOperators adjusts the same setting as the request parameter above, but on an already-submitted job.

Response

SendMessage(...) response

FieldTypeDescription
ResultstringSee Getting Started.
ErrorMessagestring[]Present only when Result is not "Success". See Getting Started.
MessageIDstringThe ID of the call you just placed.
JobNumstringTNZ's internal job number for this send.
StatusstringSee Getting Started's Common Response Enums.

Fax

TNZ's Fax API sends documents from your application to any fax machine worldwide, without physical fax hardware. Track the delivery status of each fax to confirm it arrived.

Unlike SMS/RCS/WhatsApp/Email, Fax has no free-text message field. Content comes entirely from an attached document (Attachments) or a pre-configured TemplateID.

Quick Example

import { TNZAPI } from 'tnzapi-ts';

const client = new TNZAPI({ AuthToken: '[Your Auth Token]' });

const response = await client.Messaging.Fax.SendMessage({
    Destinations: [{ ToNumber: '+6491111111' }],
    Attachments: ['path/to/doc.pdf'],
    Mode: 'Test' // Test mode
});

if (response.Result === 'Success') {
    console.log(`Success - MessageID: ${response.MessageID}`);
}

Parameters

ParameterTypeRequiredDescription
Attachmentsstring[]Yes*Local file paths (PDF/image) to fax. Each path is read from disk and base64-encoded automatically. Fax has no message-text body field; the document itself is the payload.
TemplateIDstringYes*Pre-configured fax template ID (alternative to Attachments).
DestinationsIFaxDestination[]Yes†One or more destinations. See Destination fields below.
ToNumberstringYes†Single-destination shorthand (alternative to Destinations); comma-separated for multiple.
GroupIDstringYes†Single addressbook group shorthand (alternative/addition to Destinations); comma-separated for multiple.
ContactIDstringYes†Single addressbook contact shorthand (alternative/addition to Destinations); comma-separated for multiple.
ReferencestringNoYour internal reference, returned in reports and webhooks.
MessageIDstringNoSupply your own message ID (otherwise auto-generated).
ResolutionFaxResolutionNoFax output resolution: FaxResolution.Low or FaxResolution.High.
CallerIDstringNoCaller ID displayed to the recipient's fax machine.
CSIDstringNoCalled Subscriber ID string shown in the header of the received fax.
WatermarkFolderstringNoTNZ watermark folder containing the image/template to stamp onto pages.
WatermarkFirstPagestringNoWatermark file stamped onto the first page only.
WatermarkAllPagesstringNoWatermark file stamped onto every page.
RetryAttemptsnumberNoNumber of retry attempts on send failure (busy/no answer/fax error).
RetryPeriodnumberNoMinutes to wait between retry attempts.
SendTimestringNoSchedule delivery. Combine with Timezone. Accepts YYYY-MM-DD, YYYY-MM-DD HH:mm, or ISO 8601.
TimezonestringNoWindows timezone name for SendTime, e.g. "New Zealand".
SubAccountstringNoSub-account code for billing separation.
DepartmentstringNoDepartment code.
ChargeCodestringNoCharge code for billing.
ReportTostringNoEmail address to receive delivery reports.
NotificationTypeNotificationTypeNoNotification delivery mode: None, Webhook, or Email.
WebhookCallbackURLstringNoURL for delivery status callbacks.
WebhookCallbackFormatWebhookCallbackFormatYes‡Callback format: JSON, XML, POST, or GET.
Mode'Test'NoSet to 'Test' to validate the request without sending. Omit for a live send.

*Either Attachments or TemplateID must be provided.
†At least one of Destinations, ToNumber, GroupID, or ContactID must resolve to at least one recipient, either passed directly or accumulated via AddRecipient(...) on the builder.
‡Only required when WebhookCallbackURL is set; otherwise the request is rejected with Result: "Error".

Two calling styles: every field above can be set as a property on the object passed to SendMessage({ ... }) in one call, or accumulated first via client.Messaging.Fax.AddRecipient(...)/AddAttachment(...) chained calls and finished with SendMessage(...) carrying any remaining fields. Both styles accept the same field set, and builder-accumulated recipients/attachments combine with anything also passed to the closing SendMessage(...) call. SendMessage(...) never throws for a validation failure (missing Attachments/TemplateID, an invalid phone number, and so on) - the returned promise always resolves, with Result set to "Error" and ErrorMessage describing what failed. With TypeScript's typed SendMessage(args: IFaxArgs) signature, an unknown field name is caught at compile time rather than at request time.

Destination fields (IFaxDestination)

Fax has no shared destination class across channels. Each channel's builder method (AddRecipient(...) on client.Messaging.Fax) and its Destinations array are typed against IFaxDestination, a fax-specific interface, not a common base type. Unlike some other TNZ SDKs, IFaxDestination does not declare MobilePhone, MainPhone, or EmailAddress at all: passing one of those fields to a Fax destination is a compile-time type error, not a value the SDK silently accepts and ignores.

FieldDescription
ToNumberDestination fax number, e.g. "+6491232345".
RecipientGeneric destination address, same effect as ToNumber here. This is the shape AddRecipient("+6491111111") produces when passed a bare string.
ContactIDAddressbook contact reference: sends to that contact instead of a raw number.
GroupIDAddressbook group reference: sends to all members of that group.
GroupCodeAlternative group lookup by code (instead of GroupID).
AttentionNot rendered into a merge tag (Fax has no message body). Still accepted and echoed back in status reports for your own reference.
CompanyNot rendered into a merge tag (Fax has no message body). Still accepted and echoed back in status reports for your own reference.
FirstNameNot rendered into a merge tag (Fax has no message body). Still accepted and echoed back in status reports for your own reference.
LastNameNot rendered into a merge tag (Fax has no message body). Still accepted and echoed back in status reports for your own reference.
Custom1 to Custom9Not rendered into a merge tag (Fax has no message body). Still accepted and echoed back in status reports for your own reference.

Fax has no message body to personalise, so Attention/Company/FirstName/LastName/Custom1 to Custom9 above aren't rendered into [[FirstName]]-style merge tags the way they are on other channels. Fax still accepts and echoes them back per recipient in status reports, so they're useful for your own reference and reconciliation.

Code Samples

Single destination with an attached document

The simplest way to send: a local file path in Attachments is read and base64-encoded automatically, no manual Buffer or base64 handling needed.

Security note: each entry in Attachments is a local file path that the SDK reads directly from disk (via Node's fs module) and transmits base64-encoded. The only check performed is that the file exists; there is no allow-listing or sandboxing of which paths can be read. Never build an Attachments entry from unsanitised external input (a query parameter, a request body, an uploaded file name) without validating it yourself first, since the SDK will happily read and send whatever local file that path resolves to.

const response = await client.Messaging.Fax.SendMessage({
    Destinations: [{ ToNumber: '+6491111111' }],
    Attachments: ['path/to/doc.pdf'],
    Mode: 'Test' // Test mode
});

Via the builder, with multiple destinations

AddRecipient(...) and AddAttachment(...) return this, so calls can be chained on client.Messaging.Fax before a final SendMessage(...). AddRecipient(...) accepts a bare string, an IFaxDestination object, or an array of either, so it can also add several recipients in one call.

const response = await client.Messaging.Fax
    .AddRecipient({ ToNumber: '+6491111111' })
    .AddRecipient({ ToNumber: '+6492222222' })
    .AddAttachment('path/to/doc.pdf')
    .SendMessage({ Mode: 'Test' }); // Test mode

Addressbook shorthand

Send to a single addressbook contact or every member of a group without building a Destinations array by hand.

// Send to a specific addressbook contact
const response = await client.Messaging.Fax.SendMessage({
    ContactID: 'cc5c3871-0d29-11f1-95bd-ae5f86698b98',
    Attachments: ['path/to/doc.pdf'],
    Mode: 'Test' // Test mode
});

// Send to everyone in an addressbook group
const response2 = await client.Messaging.Fax.SendMessage({
    GroupID: '11111111-2222-3333-4444-555555555555',
    Attachments: ['path/to/doc.pdf'],
    Mode: 'Test' // Test mode
});

Retry settings and watermark

Set how many times to retry a busy or unanswered fax number and how many minutes to wait between attempts, and apply a watermark image to the first page and every page of the outgoing fax.

import { FaxResolution } from 'tnzapi-ts';

const response = await client.Messaging.Fax.SendMessage({
    Destinations: [{ ToNumber: '+6491111111' }],
    Attachments: ['path/to/doc.pdf'],
    Resolution: FaxResolution.High,
    RetryAttempts: 3,
    RetryPeriod: 5,
    WatermarkFolder: 'Confidential',
    WatermarkFirstPage: 'confidential-stamp.png',
    WatermarkAllPages: 'page-marker.png',
    Mode: 'Test' // Test mode
});

Scheduled send

Delay delivery to a specific SendTime.

const response = await client.Messaging.Fax.SendMessage({
    Destinations: [{ ToNumber: '+6491111111' }],
    Attachments: ['path/to/doc.pdf'],
    SendTime: '2026-09-01 09:00',
    Timezone: 'New Zealand',
    Mode: 'Test' // Test mode
});

Response

SendMessage(...) returns Promise<MessagingApiSuccessResponseDTO | ErrorResponseDTO>. Check response.Result before reading other fields; comparing it against "Success" narrows the TypeScript type to MessagingApiSuccessResponseDTO.

Success

FieldTypeDescription
Result"Success"Always "Success" on this shape.
MessageIDstringThe ID of the fax you just sent. Use this to poll for status.
JobNumstringTNZ's internal job number for this send.
StatusstringInitial job status.

Error

FieldTypeDescription
Result"Error" | "Failed" | "Unauthorized""Error" for a validation failure caught client-side before any request is sent; "Failed"/"Unauthorized" for a rejection returned by the API itself.
ErrorMessagestring[]Human-readable error messages describing what failed. Always an array, even when it contains a single entry.

WhatsApp

Send WhatsApp messages, either free-form text or pre-approved template content, via the TNZ REST API. Set an optional fallback channel for recipients who can't be reached on WhatsApp. Find your Template ID in the Dashboard.

Quick Example

import { TNZAPI } from 'tnzapi-ts';

const client = new TNZAPI({ AuthToken: '[Your Auth Token]' });

const response = await client.Messaging.WhatsApp.SendMessage({
    Message: 'Your order has shipped!',
    ToNumber: '+64211111111',
    Mode: 'Test', // Test mode
});

if (response.Result === 'Success') {
    console.log(`Success - MessageID: ${response.MessageID}`);
}

Parameters

ParameterTypeRequiredDescription
MessagestringYes*Message body. Supports personalisation tokens [[FirstName]], [[Custom1]], etc. When sent alongside TemplateID, it must match the content of the approved template.
TemplateIDstringYes*Pre-approved WhatsApp template ID.
FromNumberstringNoRegistered WhatsApp sender number, shown on the recipient's device. Not checked by the SDK's client-side validation the way Message/TemplateID are, so confirm your account's own requirements before omitting it.
DestinationsIWhatsAppDestination[]Yes†One or more destinations, as plain object literals. See Destination fields below.
ToNumberstringYes†Single-destination shorthand. Comma-separate multiple numbers, e.g. "+64211111111,+64221111111".
GroupIDstringYes†Single addressbook group shorthand (alternative/addition to Destinations).
ContactIDstringYes†Single addressbook contact shorthand (alternative/addition to Destinations).
FallbackModeWhatsAppFallbackMode | WhatsAppFallbackMode[]NoFallback channel(s) if WhatsApp delivery fails, tried in the order given: None, RCS, SMS, or Voice. An array is joined into TNZ's wire format automatically, e.g. [WhatsAppFallbackMode.SMS, WhatsAppFallbackMode.Voice] becomes "SMS, Voice".
ReferencestringNoYour internal reference, returned in reports and webhooks.
ReportTostringNoEmail address to receive delivery reports.
Attachmentsstring[]NoLocal file paths, read and base64-encoded automatically. See the security note in Code samples below.
MessageIDstringNoSupply your own message ID (otherwise auto-generated).
SendTimestringNoSchedule delivery. Combine with Timezone.
TimezonestringNoWindows timezone name for SendTime, e.g. "New Zealand".
SubAccountstringNoSub-account code for billing separation.
DepartmentstringNoDepartment code.
ChargeCodestringNoCharge code for billing separation.
NotificationTypeNotificationTypeNoNotification delivery mode: None, Webhook, or Email.
WebhookCallbackURLstringNoURL for delivery status callbacks.
WebhookCallbackFormatWebhookCallbackFormatYes‡Callback format: JSON, XML, POST, or GET.
Mode'Test'NoSet to 'Test' to validate without sending. Leave unset for a live send.

*At least one of Message or TemplateID must be provided.
†Set via Destinations, ToNumber, GroupID, or ContactID, or via AddRecipient(...) chained on the builder.
‡Only required when WebhookCallbackURL is set; otherwise the request is rejected with Result: "Error".

Two calling styles: every field above can be passed directly in the object argument to SendMessage({ ... }) in one call, or accumulated first via client.Messaging.WhatsApp.AddRecipient(...)/AddAttachment(...) chained calls and finished with SendMessage({ ... }) carrying the remaining fields (or no arguments at all, if everything was set on the builder). SendMessage(...) always returns a Promise; validation failures resolve to Result: "Error" rather than throwing.

Destination fields

WhatsApp has no shared destination class - a destination is a plain object literal matching the IWhatsAppDestination shape. Its primary field is ToNumber; Recipient has the same effect and is what AddRecipient("+64211111111") sets when given a bare string. Destination phone numbers are validated as any phone number, landline or mobile, unlike SMS which requires a mobile number.

FieldDescription
ToNumberDestination phone number, e.g. "+64211111111".
RecipientGeneric destination number, same effect as ToNumber here. Set automatically when AddRecipient(...) is given a bare string.
FirstName / LastName / Company / AttentionPersonalisation tokens, e.g. [[FirstName]].
Custom1Custom9Arbitrary per-recipient personalisation values, [[Custom1]] … [[Custom9]].
ContactIDAddressbook contact reference: sends to that contact instead of a raw number.
GroupIDAddressbook group reference: sends to all members of that group.
GroupCodeAlternative group lookup by code (instead of GroupID).

Code Samples

Single destination shorthand

The simplest way to send: pass the message and destination directly to SendMessage({ ... }).

const response = await client.Messaging.WhatsApp.SendMessage({
    Message: 'Your order has shipped!',
    ToNumber: '+64211111111',
    Mode: 'Test', // Test mode
});

Using a pre-approved template

Pass TemplateID alongside the approved template's own text in Message, and set FromNumber to your registered WhatsApp sender.

const response = await client.Messaging.WhatsApp.SendMessage({
    TemplateID: '00000000-0000-0000-0000-000000000000',
    Message: 'Your order has shipped!',
    FromNumber: '+6491000000',
    ToNumber: '+64211111111',
    Mode: 'Test', // Test mode
});

With SMS fallback, via the builder

AddRecipient(...) accepts a bare string, an object literal, or an array of either, and can be chained before a final SendMessage({ ... }) carrying the remaining fields, here including FallbackMode for recipients who can't be reached on WhatsApp.

import { WhatsAppFallbackMode } from 'tnzapi-ts';

const response = await client.Messaging.WhatsApp
    .AddRecipient('+64211111111')
    .SendMessage({
        Message: 'Your order has shipped!',
        FromNumber: '+6491000000',
        FallbackMode: WhatsAppFallbackMode.SMS,
        Mode: 'Test', // Test mode
    });

With an attachment

A local file path in Attachments is read via fs.readFile and base64-encoded automatically - no manual base64 handling needed.

Security note: each entry in Attachments: string[] is read directly from the local filesystem with no path validation, allow-list, or traversal check. Never let untrusted or user-controlled input reach Attachments directly - doing so risks arbitrary file exfiltration from the server. The builder's AddAttachment(path) at least checks the file exists up front, returning a friendly Attachment file not found: <path> error from the next SendMessage(...) call if it's missing; the plain Attachments: [...] array in the object argument bypasses that early check entirely.

const response = await client.Messaging.WhatsApp
    .AddRecipient('+64211111111')
    .AddAttachment('path/to/invoice.pdf')
    .SendMessage({
        Message: "Here's your invoice.",
        Mode: 'Test', // Test mode
    });

Multiple destinations, with personalisation

Pass an array of destination object literals directly in Destinations for per-recipient personalisation in a single call.

const response = await client.Messaging.WhatsApp.SendMessage({
    Message: 'Hi [[FirstName]], your order #[[Custom1]] has shipped!',
    Destinations: [
        { ToNumber: '+64211111111', FirstName: 'Alice', Custom1: '4432' },
        { ToNumber: '+64221111111', FirstName: 'Bob', Custom1: '7788' },
    ],
    Mode: 'Test', // Test mode
});

Addressbook shorthand

GroupID and ContactID send to an existing Addressbook group or contact without building a Destinations array.

const response = await client.Messaging.WhatsApp.SendMessage({
    Message: 'Reminder: your subscription renews in 3 days.',
    GroupID: '4000000b-f002-4007-b00a-c00000000008',
    Mode: 'Test', // Test mode
});

Response

SendMessage(...) response

FieldTypeDescription
ResultstringSee Getting Started.
ErrorMessagestring[]Present only when Result is not "Success". See Getting Started.
MessageIDstringThe ID of the message you just sent.
JobNumstringTNZ's internal job number for this send.
StatusstringSee Getting Started's Common Response Enums.

Reschedule or abort a WhatsApp job that hasn't sent yet through the shared Actions module, by passing Channel: 'whatsapp' to client.Actions.Reschedule.SendRequest(...) or client.Actions.Abort.SendRequest(...).

Poll for status

Check delivery progress any time after sending, through the shared Reports module's Status poller, passing Channel: 'whatsapp':

const status = await client.Reports.Status.Poll({
    MessageID: response.MessageID,
    Channel: 'whatsapp',
});
FieldTypeDescription
ResultstringSee Getting Started.
MessageIDstringThe message this status is for.
JobStatusstringSee Getting Started's Common Response Enums.
JobNumstringTNZ's internal job number for this send.
AccountstringThe TNZ account that owns this job.
SubAccount / DepartmentstringEchoed from the original send.
ReferencestringEchoed from the Reference parameter.
CreatedTimeLocal / CreatedTimeUTCstringWhen the job was created.
DelayedTimeLocal / DelayedTimeUTCstringThe scheduled send time, if SendTime was set.
TimezonestringTimezone name used for scheduling.
CountnumberTotal recipients in the job.
CompletenumberRecipients processed so far.
Success / FailednumberRecipients successfully delivered / failed.
PricenumberJob total cost.
TotalRecords / RecordsPerPage / PageCount / PagenumberPagination metadata for Recipients.
RecipientsRecipientDTO[]Per-recipient results. See table below.
ErrorMessagestring[]See Getting Started.
Each entry in Recipients
FieldDescription
TypeSee Getting Started's Common Response Enums.
DestSeqTNZ's internal sequence ID for this recipient within the job.
DestinationThe recipient's phone number.
ContactIDAddressbook contact reference, if sent via ContactID/GroupID.
StatusSee Getting Started's Common Response Enums.
ResultHuman-readable delivery result for this recipient.
SentTimeLocal / SentTimeUTCWhen the message was actually sent to this recipient.
Attention / Company / Custom1Custom9Echoed personalisation fields. See Destination fields above.
RemoteIDCarrier/network-assigned identifier for this delivery, if available.
PricePer-recipient cost.

Inbound reply retrieval (the SMSReceived poller under Reports) is SMS-only in this library: its endpoint is hardcoded to SMS, and the Recipients entries above carry no reply data for WhatsApp the way SMS recipients do. There is currently no way to retrieve inbound WhatsApp replies through tnzapi-ts.

RCS

RCS (Rich Communication Services) is an enhanced mobile messaging channel; messages fall back to the server-configured fallback channel when RCS is unavailable on the recipient's device. Message/TemplateID are either/or, the same as SMS and WhatsApp. Unlike Email, Fax, or WhatsApp, IRCSArgs declares no Attachments field, so SendMessage({ ... })'s object-argument form has no typed way to attach a file. AddAttachment(...) on the builder is inherited from the shared messaging base class and does still work for RCS at runtime (it reads a local file, base64-encodes it, and sends it as Files) - it just isn't part of IRCSArgs's typed surface, and isn't documented as a supported RCS feature.

Quick Example

import { TNZAPI } from 'tnzapi-ts';

const client = new TNZAPI({ AuthToken: '[Your Auth Token]' });

const response = await client.Messaging.RCS.SendMessage({
    Message: 'Test RCS message',
    ToNumber: '+6421000001',
    Mode: 'Test', // Test mode
});

if (response.Result === 'Success') {
    console.log(`Success - MessageID: ${response.MessageID}`);
}

Parameters

ParameterTypeRequiredDescription
MessagestringYes*Message body. Supports personalisation tokens [[FirstName]], [[Custom1]], etc.
TemplateIDstringYes*Pre-configured message template ID (alternative to Message).
DestinationsIRCSDestination[]Yes†One or more destinations, as plain object literals. See Destination fields below.
ToNumberstringYes†Single-recipient shorthand, e.g. "+6421000001". Comma-separate multiple numbers, e.g. "+6421000001,+6421000002".
GroupIDstringYes†Single addressbook group shorthand (alternative/addition to Destinations).
ContactIDstringYes†Single addressbook contact shorthand (alternative/addition to Destinations).
FromNumberstringNoSender ID or number, if your account supports multiple.
FallbackModeRCSFallbackMode | RCSFallbackMode[]NoFallback channel(s) if RCS delivery fails, tried in the order given: None, SMS, Voice, WAPP. An array is joined into TNZ's wire format automatically, e.g. [RCSFallbackMode.SMS, RCSFallbackMode.Voice] becomes "SMS, Voice" on the wire.
ReferencestringNoYour internal reference, returned in reports and webhooks.
MessageIDstringNoSupply your own message ID (otherwise auto-generated).
SendTimestringNoSchedule delivery. Combine with Timezone.
TimezonestringNoWindows Timezone name for SendTime, e.g. "New Zealand", "AUS Eastern".
SubAccountstringNoSub-account code for billing separation.
DepartmentstringNoDepartment code.
ChargeCodestringNoCharge code for billing separation.
ReportTostringNoEmail address to receive delivery reports.
WebhookCallbackURLstringNoURL for delivery status callbacks.
WebhookCallbackFormatWebhookCallbackFormatYes‡Callback format: JSON, XML, POST, or GET.
NotificationTypeNotificationTypeNoNotification delivery mode: None, Webhook, or Email.
Mode'Test'NoSet to 'Test' to validate without sending. Leave unset for a live send.

*Either Message or TemplateID must be provided.
†Set via Destinations, the top-level ToNumber/GroupID/ContactID shorthand fields, or via AddRecipient(...) chained on the builder.
‡Only required when WebhookCallbackURL is set; otherwise the request is rejected with Result: "Error".

Two calling styles: every field above can be passed directly in the object argument to SendMessage({ ... }) in one call, or accumulated first via client.Messaging.RCS.AddRecipient(...) chained calls and finished with SendMessage({ ... }) carrying the remaining fields (or no arguments at all, if every recipient was already added on the builder). SendMessage(...) always returns a Promise; unlike some other channels' SDKs, RCS's SendMessage(...) does run client-side validation (missing Message/TemplateID, empty destinations, an invalid ToNumber, etc.) - a failure resolves to Result: "Error" with the reason in ErrorMessage, rather than throwing or making the HTTP call.

Destination fields (IRCSDestination)

RCS has no shared destination class - a destination is a plain object literal matching the IRCSDestination shape. Its primary field is ToNumber; Recipient has the same effect and is what AddRecipient("+6421000001") sets when given a bare string.

FieldDescription
ToNumberRecipient phone number in E.164 format (landline or mobile - the SDK does not restrict RCS destinations to mobile numbers), e.g. "+6421000001".
RecipientGeneric destination number, sent as-is regardless of channel (same effect as ToNumber here). Set automatically when AddRecipient(...) is given a bare string.
AttentionPersonalisation token [[Attention]].
FirstNamePersonalisation token [[FirstName]].
LastNamePersonalisation token [[LastName]].
CompanyPersonalisation token [[Company]].
Custom1Custom9Arbitrary per-recipient personalisation values, [[Custom1]] … [[Custom9]].
ContactIDAddressbook contact reference: sends to that contact instead of a raw number.
GroupIDAddressbook group reference: sends to all members of that group.
GroupCodeAlternative group lookup by code (instead of GroupID).

Code Samples

Single destination shorthand

The simplest way to send: pass a message and destination directly to SendMessage({ ... }).

const response = await client.Messaging.RCS.SendMessage({
    Message: 'Test RCS message',
    ToNumber: '+6421000001',
    Mode: 'Test', // Test mode
});

Via the builder, with a custom sender ID

Chain AddRecipient(...) before a final SendMessage({ ... }) that carries the remaining fields, such as a custom FromNumber sender ID.

const response = await client.Messaging.RCS
    .AddRecipient('+6421000001')
    .SendMessage({
        Message: 'Test RCS message',
        FromNumber: '+64800123456', // Sender ID, if your account supports multiple
        Mode: 'Test', // Test mode
    });

With SMS fallback

Set FallbackMode, tried if RCS delivery fails.

import { RCSFallbackMode } from 'tnzapi-ts';

const response = await client.Messaging.RCS
    .AddRecipient('+6421000001')
    .SendMessage({
        Message: 'Test RCS message',
        FallbackMode: RCSFallbackMode.SMS,
        Mode: 'Test', // Test mode
    });

Multiple destinations, with personalisation

Pass an array of destination object literals directly in Destinations for per-recipient personalisation in a single call.

const response = await client.Messaging.RCS.SendMessage({
    Message: 'Hi [[FirstName]], your order #[[Custom1]] has shipped!',
    Destinations: [
        { ToNumber: '+6421000001', FirstName: 'Alice', Custom1: '4432' },
        { ToNumber: '+6421000002', FirstName: 'Bob', Custom1: '7788' },
    ],
    Mode: 'Test', // Test mode
});

Addressbook shorthand

GroupID and ContactID send to an existing Addressbook group or contact without building a Destinations array.

// Send to everyone in an addressbook group
const toGroup = await client.Messaging.RCS.SendMessage({
    Message: 'Hello, your appointment is confirmed.',
    GroupID: '4000000b-f002-4007-b00a-c00000000002',
    Mode: 'Test', // Test mode
});

// Send to a specific addressbook contact
const toContact = await client.Messaging.RCS.SendMessage({
    Message: 'Hello, your appointment is confirmed.',
    ContactID: '8000000a-f002-4007-b00a-d00000000001',
    Mode: 'Test', // Test mode
});

Scheduled send

Delay delivery to a specific SendTime.

const response = await client.Messaging.RCS.SendMessage({
    Message: 'Staff meeting at 9am today.',
    ToNumber: '+6421000001',
    SendTime: '2026-09-01 09:00',
    Timezone: 'New Zealand',
    Mode: 'Test', // Test mode
});

Response

SendMessage(...) response

FieldTypeDescription
ResultstringSee Getting Started.
ErrorMessagestring[]Present only when Result is not "Success". See Getting Started. This is where both client-side and server-side validation errors surface.
MessageIDstringThe ID of the message you just sent.
JobNumstringTNZ's internal job number for this send.
StatusstringSee Getting Started's Common Response Enums.

Reports

client.Reports is where every polling call in this SDK lives. Unlike client.Messaging.<Channel>, no individual channel object (client.Messaging.SMS, client.Messaging.TTS, and so on) exposes a Status(...), Received(...), or Reply(...) method of its own: each channel class only implements SendMessage(...) (plus AddRecipient(...)/AddAttachment(...) where relevant). To poll for status, or to retrieve inbound SMS, go through client.Reports regardless of which channel you're checking.

client.Reports exposes three properties, each a persistent request object rather than something rebuilt on every access: .Status, .SMSReply, and .SMSReceived. Internal request state is reset after every Poll(...) call, so it's safe to call client.Reports.Status.Poll(...) repeatedly with different arguments without building a new client or a new request object each time.

Methods

MethodSignatureChannelsReturns
Status.PollPoll({ MessageID, Channel?, RecordsPerPage?, Page? })Any channel string, sent straight through (see note below); defaults to "sms" when Channel is omitted.StatusApiResponseDTO on success.
SMSReply.PollPoll({ MessageID, RecordsPerPage?, Page? })SMS only; the request always targets the SMS endpoint.SMSReplyApiResponseDTO on success.
SMSReceived.PollPoll({ TimePeriod?, DateFrom?, DateTo?, RecordsPerPage?, Page? })SMS only.SMSReceivedApiResponseDTO on success.

RecordsPerPage defaults to 100 (accepted range 1 to 999) and Page defaults to 1 across all three methods.

No client-side channel allow-list for Status.Poll. Unlike some of TNZ's other SDKs, this method does not validate Channel against a known list before sending the request. Whatever string you pass (case-sensitive, since it's placed directly into the request path) is sent to the server as-is. There's no dedicated "unknown channel" response shape here: passing a channel the server doesn't recognise, including "workflow" (which has no status endpoint at all), simply gets back whatever error the server itself returns, mapped the same as any other failed request. See Response below.

Code Samples

Poll for status, channel resolved at runtime

The channel doesn't have to be a literal in your code; here it's read back out of an object you might have stored alongside the MessageID yourself.

const storedJob = { Channel: 'sms', MessageID: 'ID123456' };

const response = await client.Reports.Status.Poll({
    Channel: storedJob.Channel,
    MessageID: storedJob.MessageID,
});

if (response.Result === 'Success') {
    console.log(`JobStatus: ${response.JobStatus}`);
    for (const recipient of response.Recipients) {
        console.log(` -> ${recipient.Destination}: ${recipient.Status} (${recipient.Result})`);
    }
}

Poll for inbound SMS

Retrieve SMS replies received in a given date range, as an alternative to configuring a webhook. Supplying only one of DateFrom/DateTo is rejected client-side; supply both together, or neither and let TimePeriod (default 1440 minutes, i.e. the last 24 hours) apply instead.

const response = await client.Reports.SMSReceived.Poll({
    DateFrom: '2026-07-01 00:00:00',
    DateTo: '2026-08-01 00:00:00',
});

if (response.Result === 'Success') {
    for (const message of response.Messages) {
        console.log(`From ${message.From}: ${message.MessageText}`);
    }
}

Poll for replies to a specific message

SMSReply.Poll(...) hits the same underlying SMS status endpoint as Status.Poll({ Channel: "sms", ... }), but returns its own SMSReplyApiResponseDTO type rather than StatusApiResponseDTO. In both cases, replies show up on Recipients[].SMSReplies, since Status.Poll also builds SMS recipients as the same SMSReplyRecipientDTO shape internally. Reach for SMSReply.Poll(...) when you only care about replies and want the narrower, dedicated type.

const response = await client.Reports.SMSReply.Poll({ MessageID: 'ID123456' });

if (response.Result === 'Success') {
    for (const recipient of response.Recipients) {
        for (const reply of recipient.SMSReplies) {
            console.log(`${recipient.Destination} replied: ${reply.MessageText}`);
        }
    }
}

Response

Every response has a Result field; check response.Result === "Success" before reading other fields. See Getting Started. Client-side validation failures (a missing MessageID, an out-of-range RecordsPerPage, supplying only one of DateFrom/DateTo, and so on) resolve with Result: "Error"; failures the server itself reports come back as whatever it sends, typically "Failed" or "Unauthorized".

Status.Poll(...)

FieldTypeDescription
Resultstring"Success" on success. See Getting Started.
MessageIDstringThe message this status relates to.
JobStatusstringThe job's current status, e.g. "Pending", "Delayed", "Completed".
JobNumstringTNZ's internal job number.
Account / SubAccount / DepartmentstringAccount and billing-separation fields on the original send.
ReferencestringYour own reference from the original send, if supplied.
CreatedTimeLocal / CreatedTimeUTCstringWhen the job was created.
DelayedTimeLocal / DelayedTimeUTCstringScheduled send time, if the job was delayed or rescheduled.
TimezonestringTimezone the local timestamps above are expressed in.
CountnumberTotal number of recipients on the job.
CompletenumberNumber of recipients processed so far.
SuccessnumberNumber of recipients delivered successfully.
FailednumberNumber of recipients that failed.
PricenumberTotal cost of the job.
TotalRecordsnumberTotal recipients matching this query, across all pages.
RecordsPerPagenumberEchoes the request's RecordsPerPage.
PageCountnumberTotal number of pages.
PagenumberEchoes the request's Page.
RecipientsRecipientDTO[]Per-recipient detail. For Channel: "sms" (including the default when Channel is omitted), each entry is a SMSReplyRecipientDTO (see below); for every other channel it's a plain RecipientDTO with no SMSReplies.

RecipientDTO fields, common to every channel:

FieldTypeDescription
TypestringThe message type this recipient relates to, e.g. "SMS".
DestSeqnumberSequence number of this destination within the job.
DestinationstringThe destination address, e.g. a mobile number in E.164 format or an email address.
ContactIDstringAddressbook contact ID, if the destination was sent via one.
StatusstringThis recipient's current status.
ResultstringFinal delivery result for this recipient, e.g. "Delivered", "Bad Number".
SentTimeLocal / SentTimeUTCstringWhen this recipient's message was sent.
Attention / CompanystringPersonalisation fields echoed back from the original send.
Custom1Custom9stringCustom personalisation fields echoed back from the original send.
RemoteIDstringCarrier or gateway-level reference for this recipient.
PricenumberCost of this recipient's message.

SMSReplyRecipientDTO adds one field on top of every RecipientDTO field above:

FieldTypeDescription
SMSRepliesSMSReplyRecipientSMSReplyDTO[]Replies received from this recipient. Empty array if none.

SMSReplyRecipientSMSReplyDTO fields:

FieldTypeDescription
ReceivedIDstringUnique ID for this reply.
ReceivedTimeLocal / ReceivedTimeUTCstringWhen the reply was received.
TimezonestringTimezone the local timestamp is expressed in.
FromstringThe mobile number the reply came from.
MessageTextstringThe reply's text content.

SMSReply.Poll(...)

SMSReplyApiResponseDTO carries the same job-level fields as StatusApiResponseDTO above (MessageID, JobStatus, JobNum, Account, SubAccount, Department, Reference, the CreatedTime/DelayedTime pairs, Timezone, Count, Complete, Success, Failed, Price) plus the same pagination fields (TotalRecords, RecordsPerPage, PageCount, Page). It is a distinct exported type from StatusApiResponseDTO, but the field set is otherwise the same. Its Recipients field is always SMSReplyRecipientDTO[] (see the tables above), never the plain RecipientDTO variant.

SMSReceived.Poll(...)

FieldTypeDescription
Resultstring"Success" on success. See Getting Started.
TotalRecordsnumberTotal messages matching this query, across all pages.
RecordsPerPagenumberEchoes the request's RecordsPerPage.
PageCountnumberTotal number of pages.
PagenumberEchoes the request's Page.
MessagesSMSReceivedDTO[]Received messages matching the query.

SMSReceivedDTO fields:

FieldTypeDescription
ReceivedIDstringUnique ID for this received message.
MessageIDstringIf this was matched as a reply, the outbound message's MessageID.
JobNumstringTNZ's internal job number for the matched outbound message, if any.
SubAccount / DepartmentstringBilling-separation fields.
ReceivedTimeLocal / ReceivedTimeUTCstringWhen the message was received.
FromstringThe mobile number the message came from.
ContactIDstringAddressbook contact ID, if matched.
MessageTextstringThe message's text content.
TimezonestringTimezone the local timestamp is expressed in.

Failure

FieldTypeDescription
Resultstring"Error", "Failed", or "Unauthorized". See Getting Started.
ErrorMessagestring[]e.g. ["Missing MessageID"] or ["DateFrom and DateTo must be supplied together"].

Actions

client.Actions is a cross-channel dispatcher for changing a job that's already been submitted. Like Reports, no individual channel object under client.Messaging.<Channel> exposes an Abort(...), Reschedule(...), Resubmit(...), or Pacing(...) method of its own in this SDK; client.Actions is the only place these calls live. Every method takes a Channel and MessageID, plus whatever extra parameter that action needs.

client.Actions exposes four properties, each a persistent request object rather than something rebuilt on every access: .Abort, .Reschedule, .Resubmit, and .Pacing. Internal request state is reset after every SendRequest(...) call, so it's safe to reuse client.Actions.Abort for multiple independent calls.

Actions Support by Channel

Not every action is valid on every channel. Abort and Reschedule work on all seven messaging channels; Resubmit and Pacing are narrower.

ChannelAbortRescheduleResubmitPacing
SMS
Email
TTS
Voice
Fax
WhatsApp
RCS

Workflow doesn't appear in this table: it has no action endpoints to dispatch to, on this facade or anywhere else in the SDK.

Methods

MethodSignatureChannel validation
Abort.SendRequestSendRequest({ MessageID, Channel })Channel just has to be non-empty; see note below.
Reschedule.SendRequestSendRequest({ MessageID, Channel, SendTime })Channel just has to be non-empty; see note below.
Resubmit.SendRequestSendRequest({ MessageID, Channel, SendTime? })Channel must be (case-insensitively) email, fax, tts, or voice.
Pacing.SendRequestSendRequest({ MessageID, Channel, NumberOfOperators })Channel must be (case-insensitively) tts or voice.

No client-side allow-list for Abort/Reschedule. Unlike Resubmit and Pacing, these two methods don't check Channel against a known list before sending the request: any non-empty string is sent straight through, in the exact case you passed it. Whether it's actually valid is left entirely to the server. The SDK's own validation error text for a missing Channel reads "Missing Channel - must be sms, email, fax, tts or voice" on both methods, but that wording predates whatsapp/rcs support and shouldn't be read as an allow-list: both are accepted equally, since the check is only for a non-empty value.

Resubmit and Pacing do check Channel against a short allow-list before sending anything. A channel outside that list, whether it's a genuine typo or a channel that's valid elsewhere (e.g. sms for Pacing), fails client-side with "<Action> is not supported for channel '<Channel>' - must be <list>", resolving with Result: "Error" rather than reaching the server at all. See Response below.

Code Samples

Abort a job, channel resolved at runtime

const storedJob = { Channel: 'fax', MessageID: 'ID123456' };

const response = await client.Actions.Abort.SendRequest({
    Channel: storedJob.Channel,
    MessageID: storedJob.MessageID,
});

if (response.Result === 'Success') {
    console.log(`Action: ${response.Action}, Status: ${response.Status}`);
}

Reschedule a job

SendTime must start with a YYYY-MM-DD date and be parseable from there, e.g. "YYYY-MM-DD hh:mm" or full ISO 8601. There's no client-side check that it's in the future; a past SendTime is left for the server to reject.

const response = await client.Actions.Reschedule.SendRequest({
    Channel: 'sms',
    MessageID: 'ID123456',
    SendTime: '2026-09-01 09:00',
});

if (response.Result === 'Success') {
    console.log(`Action: ${response.Action}, Status: ${response.Status}`);
}

Resubmit a failed job

Resubmit only supports email/fax/tts/voice; sms/whatsapp/rcs aren't resubmittable. SendTime is optional here: omit it to resubmit immediately, or supply it to schedule the resubmission.

const response = await client.Actions.Resubmit.SendRequest({
    Channel: 'fax',
    MessageID: 'ID123456',
    SendTime: '2026-09-01 09:00',
});

if (response.Result === 'Success') {
    console.log(`Action: ${response.Action}, Status: ${response.Status}`);
}

Adjust pacing

const response = await client.Actions.Pacing.SendRequest({
    Channel: 'tts',
    MessageID: 'ID123456',
    NumberOfOperators: 10,
});

if (response.Result === 'Success') {
    console.log(`Action: ${response.Action}, Status: ${response.Status}`);
}

Calling an action on a channel it doesn't support

Pacing only makes sense for tts/voice: there's no such thing as "operator pacing" for an SMS job. Calling it with Channel: "sms" doesn't throw; the returned Promise resolves to a normal Result: "Error" response, so you can handle it the same way as any other failure.

const response = await client.Actions.Pacing.SendRequest({
    Channel: 'sms',
    MessageID: 'ID123456',
    NumberOfOperators: 10,
});

if (response.Result !== 'Success') {
    console.log(response.ErrorMessage);
    // ["Pacing is not supported for channel 'sms' - must be tts or voice"]
}

Response

Every response has a Result field; check response.Result === "Success" before reading other fields. See Getting Started.

Success

All four methods return the same ActionApiResponseDTO shape on success, regardless of channel.

FieldTypeDescription
Resultstring"Success". See Getting Started.
MessageIDstringThe message this action was applied to.
JobNumstringTNZ's internal job number this action was applied to.
StatusstringThe job's status after the action, e.g. "Pending", "Delayed", "Completed".
ActionstringThe action performed, e.g. "Reschedule".

Failure

Returned whenever validation fails before the request is sent (a missing field, an unsupported Channel for Resubmit/Pacing, an unparseable SendTime) or whenever the server itself reports a failure. Client-side validation failures resolve with Result: "Error"; server-reported failures come back as whatever the server sends, typically "Failed" or "Unauthorized".

FieldTypeDescription
Resultstring"Error", "Failed", or "Unauthorized". See Getting Started.
ErrorMessagestring[]e.g. ["Missing Channel - must be sms, email, fax, tts or voice"] or ["Pacing is not supported for channel 'sms' - must be tts or voice"].

Webhooks

Webhooks let TNZ notify your own server when a message's status changes, instead of you polling client.Reports.Status.Poll(...) on a timer. Set WebhookCallbackURL and WebhookCallbackFormat on any SendMessage(...) call and TNZ posts a result payload to that URL once the message completes sending. Every messaging channel accepts these two fields: SMS, Email, Fax, Voice, TTS, WhatsApp, RCS, and Workflow.

A second, separate webhook covers inbound SMS replies. It isn't set per-send: configure it against your Sender in the TNZ Dashboard (Users > API > Reporting), and TNZ posts a payload to that URL whenever a reply arrives for that Sender. The same Dashboard settings also provide a default WebhookCallbackURL/WebhookCallbackFormat for sends that don't set their own.

Webhooks are inbound to your server: the SDK has no part in receiving them. There's no tnzapi-ts module or exported type for the payload TNZ posts back to you - the sections below cover setting the outbound fields (SDK-verified) and writing your own receiving endpoint (not part of the SDK).

WebhookCallbackFormat

The WebhookCallbackFormat enum is exported from tnzapi-ts and selects the wire format TNZ uses to deliver the callback:

ValueDescription
WebhookCallbackFormat.JSONPayload delivered as a JSON-encoded POST body. Used in every example on this page.
WebhookCallbackFormat.XMLPayload delivered as an XML-encoded POST body.
WebhookCallbackFormat.POSTPayload delivered as a POST request in TNZ's default (non-JSON, non-XML) encoding.
WebhookCallbackFormat.GETPayload delivered as a GET request against your callback URL.

If WebhookCallbackURL is set without a WebhookCallbackFormat, SendMessage(...) resolves with Result: "Error" and an ErrorMessage naming the missing field - the pairing is validated by the SDK before the request is sent.

Setting a Webhook Callback

import { TNZAPI, WebhookCallbackFormat } from 'tnzapi-ts';

const client = new TNZAPI({ AuthToken: '[Your Auth Token]' });

const response = await client.Messaging.SMS.SendMessage({
    Message: "Your order has shipped.",
    Destinations: [
        { ToNumber: "+64271234567" },
    ],
    WebhookCallbackURL: "https://yourapp.example.com/webhooks/tnz",
    WebhookCallbackFormat: WebhookCallbackFormat.JSON,
});

if (response.Result === 'Success') {
    console.log(`Queued - MessageID: ${response.MessageID}`);
}

Receiving Webhook Callbacks

tnzapi-ts has no dedicated inbound-webhook module and exports no type for the payload TNZ posts back to your server. The interface below isn't part of the SDK - it's written here purely to give the receiver examples that follow a typed shape to work against; declare it yourself, or copy it as-is.

// Not exported by tnzapi-ts - the SDK has no inbound webhook types.
interface TNZWebhookPayload {
    Version: string;
    Sender: string;
    APIKey: string;
    Type: string;
    Destination: string;
    ContactID: string | null;
    ReceivedID: string | null;
    MessageID: string | null;
    SubAccount: string | null;
    Department: string | null;
    JobNumber: string;
    SentTimeLocal: string;
    SendTimeUTC: string;
    SentTimeUTC_RFC3339: string;
    Status: string;
    Result: string;
    Message: string | null;
    Price: string | number | null;
    Detail: string;
    URL: string;
}

Payload Fields

The delivery result webhook and the inbound SMS webhook post the same set of field names; only their meaning and a few values differ. The table below covers both, noting where they diverge.

FieldTypeDescription
VersionstringWebhook payload format version.
SenderstringWebhook sender address, for correlation. Not a security signature.
APIKeystringWebhook token, for correlation. Not a security signature.
TypestringOn the delivery result webhook: the message channel, one of "SMS", "Email", "Voice" (covers Voice and TTS), or "Fax". On the inbound SMS webhook: "SMSReply" if matched to an earlier outbound message, otherwise "SMSInbound".
DestinationstringThe recipient address/number on the delivery result webhook; the replying contact's address/number on the inbound SMS webhook. Phone numbers are E.164 formatted.
ContactIDstring | nullAddressbook contact reference, if the destination matched one.
ReceivedIDstring | nullAlways null on the delivery result webhook. A unique identifier for the event on the inbound SMS webhook.
MessageIDstring | nullThe original outbound message this event relates to, where one is matched.
SubAccountstring | nullSub-account code, echoed from the original send.
Departmentstring | nullDepartment code, echoed from the original send.
JobNumberstringTNZ's internal job number for the send batch.
SentTimeLocal / SendTimeUTC / SentTimeUTC_RFC3339stringEvent timestamp in local time, UTC, and RFC 3339 UTC respectively. Note the middle field is SendTimeUTC, not SentTimeUTC - a genuine inconsistency against its SentTime* neighbours, reproduced here exactly as TNZ sends it.
StatusstringOn the delivery result webhook: "Success", "Failed", or "Pending". Always the fixed value "RECEIVED" on the inbound SMS webhook.
ResultstringOn the delivery result webhook: a channel-specific delivery result code. Always the fixed value "RECEIVED" on the inbound SMS webhook.
Messagestring | nullAlways null on the delivery result webhook. The reply text on the inbound SMS webhook.
Pricestring | number | nullCost of the message, before tax and plan credits. See the note below.
DetailstringAdditional, channel-specific detail, e.g. "SMSParts:2" or "VoiceSeconds:14".
URLstringRelated URL, if applicable.

Code Samples

The examples below use Node's built-in http module rather than a particular web framework - the SDK itself has no web framework dependency, and any framework that hands you the parsed JSON body (Express, Fastify, Koa, or otherwise) works the same way once you're inside the handler.

Node http server, handling both webhook routes

import { createServer } from 'http';

// TNZWebhookPayload as declared above - not exported by tnzapi-ts.
interface TNZWebhookPayload {
    Version: string;
    Sender: string;
    APIKey: string;
    Type: string;
    Destination: string;
    ContactID: string | null;
    ReceivedID: string | null;
    MessageID: string | null;
    SubAccount: string | null;
    Department: string | null;
    JobNumber: string;
    SentTimeLocal: string;
    SendTimeUTC: string;
    SentTimeUTC_RFC3339: string;
    Status: string;
    Result: string;
    Message: string | null;
    Price: string | number | null;
    Detail: string;
    URL: string;
}

const server = createServer((req, res) => {
    if (req.method !== 'POST') {
        res.writeHead(404).end();
        return;
    }

    let body = '';
    req.on('data', (chunk) => { body += chunk; });
    req.on('end', () => {
        const payload = JSON.parse(body) as TNZWebhookPayload;

        if (req.url === '/webhooks/tnz/result') {
            console.log(`${payload.MessageID} is now ${payload.Status} (${payload.Result})`);
        } else if (req.url === '/webhooks/tnz/inbound-sms') {
            console.log(`Inbound SMS from ${payload.Destination}: ${payload.Message}`);
        } else {
            res.writeHead(404).end();
            return;
        }

        res.writeHead(204).end();
    });
});

server.listen(3000);

Note: the as TNZWebhookPayload cast above is a compile-time assertion only - TypeScript performs no runtime validation or coercion when you cast a value parsed from JSON.parse(...). TNZ may send Price as either a JSON string or a JSON number; whichever it is, that's the runtime type payload.Price holds, regardless of what the interface declares. Code that assumes Price is always a string (calling .trim() on it, for example) can throw at runtime. Normalise it explicitly if you need a consistent type, e.g. Number(payload.Price).

Security note: TNZ's webhook requests carry a Sender field and an APIKey field in the payload, plus X-Sender and X-Timestamp headers, but none of these function as a cryptographic signature - they're plain values an attacker who discovers your callback URL could also supply. No HMAC or shared-secret signing mechanism for webhook requests is documented. Don't expose a receiver like the ones above on a public route without your own verification, e.g. a random, hard-to-guess path segment or query token, an IP allowlist for TNZ's sending range, or routing the callback through your own authenticated proxy.

Addressbook

Centralise your contacts with the Addressbook: a single source of truth that simplifies your integration and enables data-rich personalisation across every messaging channel. Manage contacts, groups, and contact-group relationships; keep contact data synchronised with your CRM, HR system, or spreadsheets; organise contacts into groups to message thousands with a single GroupID; and reduce payload size by referencing a ContactID/GroupID instead of sending full recipient details on every send. The same ContactID/GroupID values documented here work directly as ContactID/GroupID fields on a destination for any messaging channel, e.g. Email. Personalisation fields (FirstName, Company, Custom1-Custom4) work as [[FirstName]]-style merge tags in your message body, and contacts/groups created here are also usable directly in the TNZ Dashboard. Groups have no custom fields of their own; personalisation always comes from the contact.

client.Addressbook exposes four properties: .Contact, .Group, .ContactGroup, and .GroupContact. This is flat: contact-group relationships are managed through the separate ContactGroup/GroupContact properties directly off client.Addressbook, not through nested chaining off Contact or Group themselves. Each property is a single stable instance created once when the client is built, not a fresh object per access, but every method resets its own internal request state at the start of each call, so calling the same method repeatedly on the same instance (e.g. client.Addressbook.Contact.Create(...) in a loop) is safe. Every method takes a single object argument (or none, where every field is optional) and returns a Promise; validation failures resolve to Result: "Error" with an ErrorMessage rather than throwing.

Contact

Fields

FieldTypeDescription
ViewBystringWho can view this contact in the Dashboard: "Account", "SubAccount", "Department", or "No".
EditBystringWho can edit this contact in the Dashboard, same values as ViewBy.
AttentionstringPersonalisation token [[Attention]].
Titlestringe.g. "Mr", "Dr".
CompanystringPersonalisation token [[Company]].
RecipDepartmentstringThe contact's department at their company. Not related to your TNZ Department code.
FirstNamestringPersonalisation token [[FirstName]].
LastNamestringPersonalisation token [[LastName]].
PositionstringJob title.
StreetAddress / Suburb / City / State / Country / PostcodestringPostal address fields.
MainPhonestringPrimary phone number.
DirectPhonestringDirect-dial phone number.
AltPhone1 / AltPhone2stringUp to 2 additional phone numbers.
MobilePhonestringMobile number, used as the SMS/WhatsApp/RCS destination when sending via ContactID.
FaxNumberstringFax destination.
EmailAddressstringEmail destination. Validated client-side against a basic email format before the request is sent; an invalid value resolves to Result: "Error" without a network call.
WebAddressstringWebsite URL.
Custom1-Custom4stringPersonalisation tokens [[Custom1]]-[[Custom4]].
TimezonestringTimezone for this contact's local timestamps.

Every field here is optional and every field is a plain string: there's no Enums.ViewByOptions-style helper type for ViewBy/EditBy, just the literal strings above. There's also no Notes field and no ExType/ExID external-system-correlation fields on this SDK's Contact.

Create

Pass fields directly in the object argument to Create({ ... }). The response wraps the created record under .Contact, not at the top level.

import { TNZAPI } from 'tnzapi-ts';

const client = new TNZAPI({ AuthToken: '[Your Auth Token]' });

const response = await client.Addressbook.Contact.Create({
    Attention: 'API Test',
    FirstName: 'API',
    LastName: 'Test',
    MobilePhone: '+64211231234',
    EmailAddress: 'test@example.com',
    MainPhone: '+6491112222',
});

if (response.Result === 'Success') {
    console.log(`Created ContactID=${response.Contact.ContactID}`);
}

Detail

Look up a contact's stored fields by ContactID, passed as an object, not a bare string. A missing or empty ContactID resolves to Result: "Error" ("Missing ContactID") rather than sending a broken request.

const details = await client.Addressbook.Contact.Detail({ ContactID: response.Contact.ContactID });

if (details.Result === 'Success') {
    console.log(`${details.Contact.FirstName} ${details.Contact.LastName}, ${details.Contact.EmailAddress}`);
}

Update and Delete

Change or remove a contact. Both take a plain object with ContactID; there's no shorthand for passing a prior response object in place of the ID, so extract ContactID from it yourself. Update({ ... }) is a partial PATCH: only the fields you pass are changed.

const updated = await client.Addressbook.Contact.Update({
    ContactID: response.Contact.ContactID,
    Company: 'Example Company',
});

await client.Addressbook.Contact.Delete({ ContactID: response.Contact.ContactID });

List

Page through your full contact list. There's no separate Search(...) method on this SDK's Contact: List({ ... }) is the only way to enumerate contacts, and it only covers the requested page, defaulting to RecordsPerPage: 100, Page: 1. This SDK never auto-walks every page on your behalf.

const page = await client.Addressbook.Contact.List({ RecordsPerPage: 100, Page: 1 });

if (page.Result === 'Success') {
    for (const contact of page.Contacts) {
        console.log(`${contact.ContactID}: ${contact.FirstName} ${contact.LastName}`);
    }
}

Group

Fields

FieldTypeDescription
GroupNamestringDisplay name for the group.
SubAccountstringSub-account code.
DepartmentstringDepartment code.
ViewEditBystringWho can view and edit this group in the Dashboard: "Account", "SubAccount", "Department", or "No" (case-insensitive). Unlike Contact, Group has one combined permission field rather than separate ViewBy/EditBy fields. An unrecognised value resolves to Result: "Error" before any network call, on both Create and Update.

Create

Same object-argument pattern as Contact. GroupName is required; a missing value resolves to Result: "Error". GroupID/GroupCode are server-assigned and returned on the response, not something you set.

const response = await client.Addressbook.Group.Create({
    GroupName: 'API Test Group',
    SubAccount: 'SALES',
    ViewEditBy: 'SubAccount',
});

if (response.Result === 'Success') {
    console.log(`Created GroupID=${response.Group.GroupID}, GroupCode=${response.Group.GroupCode}`);
}

Detail, Update, Delete, and List

Manage a group the same way as a contact: look up, rename, remove, or page through all groups. Detail/Update/Delete all accept either GroupID or GroupCode in the object argument (either works; at least one is required). List defaults to RecordsPerPage: 100, Page: 1, same as Contact.

const details = await client.Addressbook.Group.Detail({ GroupID: response.Group.GroupID });

await client.Addressbook.Group.Update({ GroupID: response.Group.GroupID, GroupName: 'Renamed Group' });

await client.Addressbook.Group.Delete({ GroupID: response.Group.GroupID });

const page = await client.Addressbook.Group.List({ RecordsPerPage: 100, Page: 1 });

Contact ↔ Group relationships

ContactGroup (contact's-side view) and GroupContact (group's-side view) both manage the same underlying membership, and both support the same four operations: List(...), Create(...), Delete(...), and Detail(...). The method names are Create/Delete, not Add/Remove, and every call takes a single object argument, never a bare ID string. ContactGroup.Create(...)/Delete(...) and GroupContact.Create(...)/Delete(...) dispatch to the exact same underlying endpoints; only which side you call from differs, not the wire request.

Important correctness note: unlike some other TNZ SDKs, Detail(...) on both ContactGroup and GroupContact is a real, dedicated lookup endpoint. It is not synthesised by paging through List(...) and scanning for a match, and it takes no RecordsPerPage/Page parameters at all, because it doesn't need them. A Result other than "Success" from Detail(...) reflects what the server actually returned for that exact ContactID/GroupID pair, immediately and correctly, regardless of how many groups a contact belongs to or what page a membership would otherwise fall on in a list. You do not need to increase page size or inspect PageCount to trust a "not found" result here.

A real asymmetry between the two, worth knowing before you rely on it: IContactGroupListArgs (the argument type for ContactGroup.List(...)) declares an optional Contact object field alongside ContactID, but the implementation never reads it. ContactGroup.List({ Contact: someContact }) compiles, but resolves to Result: "Error" ("Missing ContactID") at runtime, because only a directly supplied ContactID is honoured. GroupContact.List(...) does not have this gap: it accepts a Group object in addition to GroupID/GroupCode, and flattens it to GroupID internally before validating. In other words, always pass ContactID directly to ContactGroup.List(...); don't rely on a Contact object being unwrapped for you the way it is on GroupContact.List(...), or on ContactGroup's own Create/Delete/Detail, which do flatten a Contact/Group object correctly.

// Groups a contact belongs to
const groups = await client.Addressbook.ContactGroup.List({ ContactID: contactID });

// Add a contact to a group, from the contact's side
const addResult = await client.Addressbook.ContactGroup.Create({ ContactID: contactID, GroupID: groupID });

if (addResult.Result === 'Success') {
    console.log(`Added to group: ${addResult.Group.GroupName}`);
}

// Look up a single contact-group relation - a real dedicated endpoint, not a scan over List()
const relation = await client.Addressbook.ContactGroup.Detail({ ContactID: contactID, GroupID: groupID });

// Remove a contact from a group
await client.Addressbook.ContactGroup.Delete({ ContactID: contactID, GroupID: groupID });

// Contacts belonging to a group
const contacts = await client.Addressbook.GroupContact.List({ GroupID: groupID });

if (contacts.Result === 'Success') {
    for (const contact of contacts.Contacts) {
        console.log(`${contact.FirstName} ${contact.LastName}`);
    }
}

// Add a contact to a group, from the group's side (same wire endpoint as above)
const groupAddResult = await client.Addressbook.GroupContact.Create({ GroupID: groupID, ContactID: contactID });

if (groupAddResult.Result === 'Success') {
    console.log(`Added ${groupAddResult.Contact.FirstName} ${groupAddResult.Contact.LastName} to group`);
}

// Remove a contact from a group, from the group's side
await client.Addressbook.GroupContact.Delete({ GroupID: groupID, ContactID: contactID });

// Look up a single group-contact relation - also a real dedicated endpoint
const groupRelation = await client.Addressbook.GroupContact.Detail({ GroupID: groupID, ContactID: contactID });

Response

Every Addressbook result carries Result and, on anything other than success, ErrorMessage: check Result === "Success" before reading other fields; see Getting Started. List-style results additionally carry TotalRecords/RecordsPerPage/PageCount/Page (all number) for pagination. Unlike some other TNZ SDKs, the record itself is nested under a named property (.Contact or .Group), not spread across the top level of the response.

Contact.Create(...)/Detail(...)/Update(...)/Delete(...) response

FieldTypeDescription
ResultstringSee Getting Started.
ErrorMessagestring[]Present only when Result is not "Success". See Getting Started.
ContactContactModelThe contact record. Present only on success.
Contact.ContactIDstringThe contact's ID.
Contact.OwnerstringThe TNZ user who owns this contact.
Contact.CreatedTimeLocal / Contact.CreatedTimeUTCstringWhen the contact was created, in local time and UTC respectively.
Contact.UpdatedTimeLocal / Contact.UpdatedTimeUTCstringWhen the contact was last updated.
Contact.TimezonestringTimezone the local timestamps above are expressed in.
every Contact field abovestringEchoed back on Contact, e.g. Contact.FirstName, Contact.EmailAddress, Contact.Custom1-Contact.Custom4. See the Fields table above.

Contact.List(...) response

FieldTypeDescription
ResultstringSee Getting Started.
ErrorMessagestring[]Present only when Result is not "Success".
TotalRecords / RecordsPerPage / PageCount / PagenumberPagination metadata for this page.
ContactsContactModel[]The matching contacts for this page, each shaped like Contact.Detail(...)'s Contact field.

Group.Create(...)/Detail(...)/Update(...)/Delete(...) response

FieldTypeDescription
ResultstringSee Getting Started.
ErrorMessagestring[]Present only when Result is not "Success".
GroupGroupModelThe group record. Present only on success.
Group.GroupIDstringThe group's ID.
Group.GroupCodestringServer-assigned lookup code. Read-only; there's no matching field in the Fields table above to set it.
Group.GroupName / Group.SubAccount / Group.Department / Group.ViewEditBystringEchoed back. See the Fields table above.
Group.AccessControlstring"Limited" or "Granted". Read-only.
Group.OwnerstringThe TNZ user who owns this group.
Group.CreatedTimeLocal / Group.CreatedTimeUTCstringWhen the group was created. Unlike Contact, Group has no Updated* timestamps.
Group.TimezonestringTimezone the local timestamp above is expressed in.

Group.List(...) response

FieldTypeDescription
ResultstringSee Getting Started.
ErrorMessagestring[]Present only when Result is not "Success".
TotalRecords / RecordsPerPage / PageCount / PagenumberPagination metadata for this page.
GroupsGroupModel[]The groups for this page, each shaped like Group.Detail(...)'s Group field.

ContactGroup.List(...) response

FieldTypeDescription
ResultstringSee Getting Started.
ErrorMessagestring[]Present only when Result is not "Success".
TotalRecords / RecordsPerPage / PageCount / PagenumberPagination metadata for this page.
ContactContactModelThe contact whose groups you're listing.
GroupsGroupModel[]The groups this contact belongs to, for this page.

GroupContact.List(...) response

FieldTypeDescription
ResultstringSee Getting Started.
ErrorMessagestring[]Present only when Result is not "Success".
TotalRecords / RecordsPerPage / PageCount / PagenumberPagination metadata for this page.
GroupGroupModelThe group whose members you're listing.
ContactsContactModel[]The contacts belonging to this group, for this page.

ContactGroup.Create(...)/Delete(...)/Detail(...) response

FieldTypeDescription
ResultstringSee Getting Started. On Detail(...) this reflects the actual dedicated lookup, not a client-side scan; see the correctness note above.
ErrorMessagestring[]Present only when Result is not "Success".
ContactContactModelThe contact side of this relation.
GroupGroupModelThe group side of this relation.

GroupContact.Create(...)/Delete(...)/Detail(...) response

FieldTypeDescription
ResultstringSee Getting Started. On Detail(...) this reflects the actual dedicated lookup, not a client-side scan; see the correctness note above.
ErrorMessagestring[]Present only when Result is not "Success".
GroupGroupModelThe group side of this relation.
ContactContactModelThe contact side of this relation.

These last two result types carry no pagination fields, unlike the List results above; each always describes exactly one contact-group relation.

OptOut

OptOut manages your suppression list: destinations or addressbook contacts who have asked not to be contacted. Once a destination is opted out, sending to it doesn't fail outright - the API accepts the request, but delivery is blocked and the report reflects the suppression. Opt-outs are scoped by DestType, and optionally by SubAccount and Department, so a contact can be opted out of SMS marketing while still receiving Email alerts.

Access it directly as client.OptOut: there's no intermediate Configuration facade in this SDK.

DestType is a plain string field, not an enum or union type. The SDK does not validate it against a fixed list of channel names client-side - Create(...) only checks that it is non-empty before sending the request. Common values are "SMS", "Email", "Fax", and "Voice", matching the channel names used elsewhere in this SDK.

Fields

FieldTypeRequiredDescription
DestinationstringYesThe destination to suppress, e.g. "+6421003004" or an email address.
DestTypestringYesThe channel this entry applies to. See above.
ContactIDstringNoAddressbook contact reference to associate with this entry, in addition to Destination.
SubAccountstringNoScope this entry to a sub-account. Leave unset to apply to all sub-accounts.
DepartmentstringNoScope this entry to a department. Leave unset to apply to all departments.
StopMessagestringNoThe opt-out phrase detected, e.g. "Stop sending me these messages".
NotesstringNoFree-text notes.

Destination and DestType are both required by Create(...); unlike some other SDKs in this family, ContactID is not an alternative to Destination here, only a supplementary reference alongside it.

Code Samples

List opt-outs

List(...) pages through the suppression list, optionally filtered by DestType, TimePeriod (days), or ContactID. RecordsPerPage defaults to 100 and Page defaults to 1 when omitted.

import { TNZAPI, ErrorResponseDTO } from 'tnzapi-ts';

const client = new TNZAPI({ AuthToken: '[Your Auth Token]' });

const result = await client.OptOut.List({
    DestType: 'SMS',
    TimePeriod: 30,
});

if (!(result instanceof ErrorResponseDTO)) {
    console.log(`Total opted out: ${result.TotalRecords}`);
    result.OptOuts.forEach((entry) => {
        console.log(`${entry.Destination} (${entry.DestType})`);
    });
}

Create an OptOut entry

Suppress future sends to a destination on a specific channel. Both Destination and DestType are required.

const response = await client.OptOut.Create({
    Destination: '+6421003004',
    DestType: 'SMS',
    Notes: 'Requested via support call',
});

if (response.Result === 'Success') {
    console.log(`Created OptOut ID: ${response.ID}`);
}

Note: the response field is ID, not OptOutID. The request-side field used by Detail(...) and Delete(...) below is OptOutID - take the value from response.ID and pass it as OptOutID in the next call, as shown here:

const optoutId = response.ID;

const detail = await client.OptOut.Detail({ OptOutID: optoutId });

Look up an OptOut entry

Detail(...) takes the OptOutID returned as ID on a prior Create(...) or List(...) entry.

const detail = await client.OptOut.Detail({
    OptOutID: 'a1b2c3d4-e5f6-7890-1234-567890abcdef',
});

if (detail.Result === 'Success') {
    console.log(`${detail.Destination} is opted out for ${detail.DestType}`);
}

Remove an OptOut entry

Delete(...) also takes OptOutID, removing the entry so future sends to that destination are no longer suppressed.

const deleted = await client.OptOut.Delete({
    OptOutID: 'a1b2c3d4-e5f6-7890-1234-567890abcdef',
});

if (deleted.Result === 'Success') {
    console.log('OptOut entry removed');
}

Response

Every OptOut result carries Result: check Result === 'Success' before reading other fields, or narrow with instanceof ErrorResponseDTO as shown in the List(...) sample above. See Getting Started.

Create(...)/Detail(...)/Delete(...) response

FieldTypeDescription
ResultstringSee Getting Started.
IDstringThis entry's ID. Note the field is plain ID, not OptOutID - see the note above.
Destination / DestType / ContactID / Department / SubAccount / StopMessage / NotesstringEchoed back. See the Fields table above.
OriginalMessagestringThe original inbound message that triggered the opt-out, when it was created automatically from a reply such as STOP rather than via this API. Not settable.
CreatedTimeLocal / CreatedTimeUTC / CreatedTimeUTC_RFC3339stringWhen the entry was created, in local time, UTC, and RFC3339 UTC respectively. Not settable.
UpdatedTimeLocal / UpdatedTimeUTC / UpdatedTimeUTC_RFC3339stringWhen the entry was last updated. Not settable.
TimezonestringTimezone the local timestamps above are expressed in.

List(...) response

FieldTypeDescription
ResultstringSee Getting Started.
TotalRecords / RecordsPerPage / PageCount / PagenumberPagination.
OptOutsOptOutApiResponseDTO[]The matching entries for this page, each shaped like the Detail(...) response fields above.