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.tsdeclarations. Also works from ESM projects ("type": "module"inpackage.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 / runtime | Supported | Notes |
|---|---|---|
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 Functions | ✓ | Node.js runtime required. |
| Electron | ✓ (main process only) | Not the renderer process, unless nodeIntegration is enabled. |
Browser (client-side React/Vue/Angular, plain <script>) | Not supported | No 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
- Login to the TNZ Dashboard
- Navigate to 'Users'
- Create a new user or select an existing one
- Enable API access (if it's not already enabled)
- Click on the 'API' tab
- Enable 'Auth Token' and create a new Auth Token
- 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
- Login to the TNZ Dashboard
- Navigate to 'Users'
- Click on your API user
- Click on the 'API' tab
- Click the refresh/recycle button in the Auth Token section
- 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.
| Variable | Purpose | Default |
|---|---|---|
TNZ_AUTH_TOKEN | Fallback 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_URL | Overrides 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_SSL | Must 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_HTTP | Must 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();
Behaviour
Every request has a 30-second timeout and no automatic retries: a timed-out or failed request resolves to an error response rather than being retried for you. Every request also sends a fixed User-Agent: tnzapi-ts/3.00 header.
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';
| Enum | Members | Used for |
|---|---|---|
WebhookCallbackFormat | JSON, XML, POST, GET | The WebhookCallbackFormat request field, controlling how inbound delivery/reply events are posted to your WebhookCallbackURL. |
NotificationType | None, Webhook, Email | The NotificationType request field. |
AnswerPhoneMode | NDAS, NDAF, DAS, DAF | Answering-machine handling on TTS/Voice calls. |
TTSVoice | Female1, Male1, Nicole, Russell, Amy, Brian, Emma | TTS's Voice field. |
FaxResolution | Low, High | Fax's Resolution field. |
SMSFallbackMode | None, RCS, WAPP, Voice | SMS's FallbackMode field. |
WhatsAppFallbackMode | None, RCS, SMS, Voice | WhatsApp's FallbackMode field. |
RCSFallbackMode | None, SMS, Voice, WAPP | RCS'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.
No top-level EmailAddress shortcut
Workflow's top-level shorthand fields are ToNumber, MainPhone, GroupID, and ContactID only: there is no top-level EmailAddress field. To route a Workflow send to an email address, set EmailAddress on an entry inside Destinations instead (see the omni-channel sample below).
Inline destinations create or update an addressbook contact
Supplying ToNumber, MainPhone, or EmailAddress inline, whether inside a Destinations entry or via the top-level ToNumber/MainPhone shorthand, creates a new addressbook contact for that recipient, or updates a matching existing one, unless the destination also carries a ContactID or GroupID. This applies only to Workflow: no other channel in this library creates addressbook entries as a side effect of sending.
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
| Parameter | Type | Required | Description |
|---|---|---|---|
WorkflowTemplateID | string (uuid) | Yes | ID of the Workflow Template to trigger, built in the Dashboard. |
Destinations | IWorkflowDestination[] | No* | One or more destinations. See Destination Fields below. |
ToNumber | string | No* | Single-destination shorthand for a phone number, e.g. "+64211111111". Comma-separated values create multiple destinations. |
MainPhone | string | No* | 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. |
GroupID | string | No* | Single addressbook group shorthand. Comma-separated values create multiple destinations. |
ContactID | string | No* | Single addressbook contact shorthand. Comma-separated values create multiple destinations. |
Reference | string | No | Your internal reference, returned in reports and webhooks. |
SendTime | string | No | Schedule the trigger, e.g. "2026-09-01 09:00". Combine with Timezone. |
Timezone | string | No | Windows timezone name for SendTime, e.g. "New Zealand". |
SubAccount | string | No | Sub-account code for billing separation. |
Department | string | No | Department code. |
ChargeCode | string | No | Charge code for billing and reporting. |
MessageID | string | No | Supply your own message ID, otherwise one is auto-generated. |
WebhookCallbackURL | string | No | URL for delivery status callbacks. Requires WebhookCallbackFormat to also be set. |
WebhookCallbackFormat | WebhookCallbackFormat | Yes‡ | Callback format: "JSON", "XML", "POST", or "GET". |
Mode | 'Test' | No | Set 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".
Required field validation
Workflow.SendMessage(...) validates client-side before making any HTTP call. In order: AuthToken must be set on the client, WebhookCallbackFormat must be set if WebhookCallbackURL is, SendTime must parse if set, at least one destination must resolve, Mode must be "Test" if set, and finally WorkflowTemplateID must be present. Any failure returns a response with Result: "Error" and a matching ErrorMessage entry (e.g. "Empty Destination(s)" or "Missing WorkflowTemplateID"), without contacting the TNZ API at all.
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.
| Field | Description |
|---|---|
ContactID | Addressbook contact reference. Sends to that contact instead of a raw address. |
GroupID | Addressbook group reference. Sends to all members of that group. |
GroupCode | Alternative group lookup by code. |
ToNumber | Phone 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. |
MainPhone | A 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. |
EmailAddress | Email 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. |
Recipient | Generic fallback field, used internally by AddRecipient(string) when adding a bare string. |
Attention | Personalisation token override [[Attention]]. |
FirstName | Personalisation token override [[FirstName]]. |
LastName | Personalisation token override [[LastName]]. |
Company | Personalisation token override [[Company]]. |
Custom1–Custom9 | Personalisation 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).
| Field | Type | Description |
|---|---|---|
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. |
MessageID | string | The ID of the Workflow run you just triggered. Present on success. |
JobNum | string | Job number for the triggered run. Present on success. |
Status | string | Initial status of the triggered run, e.g. "Queued". Present on success. |
ErrorMessage | string[] | 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 viaAttachments([[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
| Parameter | Type | Required | Description |
|---|---|---|---|
Reference | string | No | Your internal reference, returned in reports and webhooks. |
Message | string | Yes* | Message body. Supports personalisation tokens [[FirstName]], [[Custom1]], etc. Max length 1000 characters. |
TemplateID | string | Yes* | Pre-configured message template ID (alternative to Message). |
Destinations | ISMSDestination[] | Yes† | One or more destinations. See Destination fields below. |
ToNumber | string | Yes† | Single-recipient shorthand for Destinations: [{ ToNumber }]. Comma-separated values create multiple destinations. |
GroupID | string | Yes† | Single-recipient shorthand for Destinations: [{ GroupID }]. Comma-separated values create multiple destinations. |
ContactID | string | Yes† | Single-recipient shorthand for Destinations: [{ ContactID }]. Comma-separated values create multiple destinations. |
SendTime | string | No | Schedule delivery. Combine with Timezone. |
Timezone | string | No | Windows timezone name for SendTime (e.g. "New Zealand"). |
SubAccount | string | No | Sub-account code for billing separation. |
Department | string | No | Department code. |
ChargeCode | string | No | Charge code. |
MessageID | string | No | Supply your own message ID (otherwise auto-generated). |
ReportTo | string | No | Email address to receive delivery reports. |
WebhookCallbackURL | string | No | URL for delivery status callbacks. |
WebhookCallbackFormat | WebhookCallbackFormat | Yes‡ | Callback format: JSON / XML / POST / GET. Required if WebhookCallbackURL is set; otherwise the request is rejected with Result: "Error". |
NotificationType | NotificationType | No | Notification delivery mode: None, Webhook, or Email. |
Mode | 'Test' | No | Set to "Test" to validate the request without sending. Any other non-empty value is rejected. |
Attachments | string[] | No | Local 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. |
FallbackMode | SMSFallbackMode | SMSFallbackMode[] | No | Fallback 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. |
SMSEmailReply | string | No | Email address to receive SMS replies. |
CharacterConversion | boolean | No | Convert 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)
| Field | Description |
|---|---|
ToNumber | Destination phone number, e.g. "+64211111111". |
Recipient | Generic fallback field produced internally by the string form of the builder API - AddRecipient("+64211111111") sets { Recipient: "+64211111111" }. Same effect as ToNumber for SMS. |
Attention | Personalisation token [[Attention]]. |
FirstName | Personalisation token [[FirstName]]. |
LastName | Personalisation token [[LastName]]. |
Company | Personalisation token [[Company]]. |
Custom1–Custom9 | Arbitrary per-recipient personalisation values, [[Custom1]] … [[Custom9]]. |
ContactID | Addressbook contact reference: sends to that contact instead of a raw number. |
GroupID | Addressbook group reference: sends to all members of that group. |
GroupCode | Alternative 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.
| Field | Type | Description |
|---|---|---|
Result | string | "Success" on success; "Error", "Failed", or "Unauthorized" on failure. |
ErrorMessage | string[] | Present on the error variant only; human-readable error strings. |
MessageID | string | The ID of the message you just sent. |
JobNum | string | TNZ's internal job number for this send. |
Status | string | Initial 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[].
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
MessageID | string | The message this status is for. |
JobStatus | string | e.g. "Completed", "Processing", "Delayed". |
JobNum | string | TNZ's internal job number for this send. |
Account | string | The TNZ account that owns this job. |
SubAccount / Department | string | Echoed from the original send. |
Reference | string | Echoed from the Reference parameter. |
CreatedTimeLocal / CreatedTimeUTC | string | When the job was created, in local time and UTC. |
DelayedTimeLocal / DelayedTimeUTC | string | The scheduled send time, if SendTime was set. |
Timezone | string | Timezone name used for scheduling. |
Count | number | Total recipients in the job. |
Complete | number | Recipients processed so far. |
Success / Failed | number | Recipients successfully delivered / failed. |
Price | number | Job total cost. |
TotalRecords / RecordsPerPage / PageCount / Page | number | Pagination metadata for Recipients, controlled by the call's RecordsPerPage/Page parameters (default 100 per page, max 999; page default 1). |
Recipients | RecipientDTO[] / SMSReplyRecipientDTO[] | Per-recipient results. See table below. |
Recipient object (each entry in Recipients)
| Field | Type | Description |
|---|---|---|
Type | string | Recipient channel type. |
DestSeq | number | TNZ's internal sequence ID for this recipient within the job. |
Destination | string | The recipient's phone number. |
ContactID | string | Addressbook contact reference, if sent via ContactID/GroupID. |
Status | string | Delivery status for this recipient. |
Result | string | Human-readable delivery result for this recipient. |
SentTimeLocal / SentTimeUTC | string | When the message was actually sent to this recipient. |
Attention / Company / Custom1–Custom9 | string | Echoed personalisation fields. See Destination fields above. |
RemoteID | string | Carrier/network-assigned identifier for this delivery, if available. |
Price | number | Per-recipient cost. |
SMSReplies | SMSReplyRecipientSMSReplyDTO[] | 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)
| Field | Type | Description |
|---|---|---|
ReceivedID | string | Unique identifier for this reply. |
ReceivedTimeLocal / ReceivedTimeUTC | string | When the reply was received. |
Timezone | string | Timezone name for ReceivedTimeLocal. |
From | string | The replying phone number. |
MessageText | string | The reply body. |
Reports.SMSReceived.Poll(...) response
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
TotalRecords / RecordsPerPage / PageCount / Page | number | Pagination metadata for Messages, controlled by the call's RecordsPerPage/Page parameters. |
Messages | SMSReceivedDTO[] | Inbound SMS messages. See table below. |
Message object (each entry in Messages)
| Field | Description |
|---|---|
ReceivedID | Unique identifier for this message. |
MessageID | The original outbound message this replies to, if determinable. |
JobNum | The original send job's number, if applicable. |
SubAccount / Department | Echoed billing codes from the original send. |
ReceivedTimeLocal / ReceivedTimeUTC | When the message was received, in local time and UTC. |
From | The sender's phone number. |
ContactID | Addressbook contact reference, if the sender matched one. |
MessageText | The message body. |
Timezone | Timezone name for ReceivedTimeLocal. |
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
| Parameter | Type | Required | Description |
|---|---|---|---|
EmailSubject | string | Yes | Subject line for the email. |
MessagePlain | string | Yes* | Plain-text body. |
MessageHTML | string | Yes* | HTML body. Can be combined with MessagePlain for a multipart email. |
TemplateID | string | Yes* | Pre-configured message template ID (alternative to MessagePlain/MessageHTML). |
Destinations | IEmailDestination[] | Yes† | One or more destinations, as plain object literals. See Destination fields below. |
EmailAddress | string | Yes† | Single-destination shorthand. Comma-separate multiple addresses, e.g. "a@test.com,b@test.com". |
GroupID | string | Yes† | Single addressbook group shorthand (alternative/addition to Destinations). |
ContactID | string | Yes† | Single addressbook contact shorthand (alternative/addition to Destinations). |
FromEmail | string | No | Sender address. Leave blank to use your API username. |
From | string | No | Legacy alternate sender field. Prefer FromEmail. |
SMTPFrom | string | No | Legacy alternate sender field, rarely needed. Prefer FromEmail. |
ReplyTo | string | No | Reply-To address: replies from the recipient are sent here instead of FromEmail. |
CCEmail | string | No | Tracked CC address added to the email (chargeable, per recipient). |
BCCEmail | string | No | Untracked BCC address added to the email (chargeable, per recipient). |
Attachments | string[] | No | Local file paths, read and base64-encoded automatically. See the security note in Code Samples below. |
Reference | string | No | Your internal reference, returned in reports and webhooks. |
MessageID | string | No | Supply your own message ID (otherwise auto-generated). |
SendTime | string | No | Schedule delivery. Combine with Timezone. |
Timezone | string | No | Windows timezone name for SendTime, e.g. "New Zealand". |
SubAccount | string | No | Sub-account code for billing separation. |
Department | string | No | Department code. |
ChargeCode | string | No | Charge code for billing separation. |
ReportTo | string | No | Email address to receive delivery reports. |
NotificationType | NotificationType | No | Notification delivery mode: None, Webhook, or Email. |
WebhookCallbackURL | string | No | URL for delivery status callbacks. |
WebhookCallbackFormat | WebhookCallbackFormat | Yes‡ | Callback format: JSON, XML, POST, or GET. |
Mode | 'Test' | No | Set 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.
| Field | Description |
|---|---|
EmailAddress | Destination email address, e.g. "email.one@test.com". |
Recipient | Generic 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 / Attention | Personalisation tokens, e.g. [[FirstName]]. |
Custom1–Custom9 | Arbitrary per-recipient personalisation values, [[Custom1]] … [[Custom9]]. |
ContactID | Addressbook contact reference: sends to that contact instead of a raw address. |
GroupID | Addressbook group reference: sends to all members of that group. |
GroupCode | Alternative 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
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
ErrorMessage | string[] | Present only when Result is not "Success". See Getting Started. |
MessageID | string | The ID of the message you just sent. |
JobNum | string | TNZ's internal job number for this send. |
Status | string | See 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
| Parameter | Type | Required | Description |
|---|---|---|---|
MessageToPeople | string | Yes* | Message read aloud when a live person answers. Supports personalisation tokens such as [[FirstName]]. |
TemplateID | string | Yes* | Pre-configured message template ID (alternative to MessageToPeople). |
Destinations | ITTSDestination[] | Yes† | One or more destinations. See Destination fields below. |
ToNumber | string | Yes† | Single-destination shorthand, e.g. "+64211111111". Comma-separate for multiple. Resolves internally to a MainPhone destination. |
GroupID | string | Yes† | Single addressbook group to call (alternative/addition to Destinations). Comma-separated for multiple. |
ContactID | string | Yes† | Single addressbook contact to call (alternative/addition to Destinations). Comma-separated for multiple. |
Reference | string | No | Your internal reference, echoed back in reports and webhooks. |
MessageToAnswerPhones | string | No | Message read when an answering machine is detected. |
AnswerPhoneMode | AnswerPhoneMode | No | How to handle an answering machine: "NDAS", "NDAF", "DAS", or "DAF". Default "NDAS". See AnswerPhoneMode values below. |
Keypads | ITTSKeypad[] | No | Keypad menu options. See Keypad fields below. |
KeypadOptionRequired | boolean | No | Require the caller to press a key before the call proceeds. Default false. |
CallRouteMessageOnWrongKey | string | No | Message played if an invalid key is pressed. |
CallRouteMessageToPeople | string | No | Message played before connecting the caller to an operator. |
CallRouteMessageToOperators | string | No | Message played to the operator receiving the routed call. |
NumberOfOperators | number | No | Live operators available for keypad-routed calls. The SDK sends 0 when left unset; there's no client-side minimum enforced. |
RetryAttempts | number | No | Retry attempts on no-answer/busy. Maximum 5. |
RetryPeriod | number | No | Minutes between retry attempts. Maximum 60. |
CallerID | string | No | Caller ID shown to the recipient. |
Voice | TTSVoice | No | Synthesised voice: "Female1", "Male1", "Nicole", "Russell", "Amy", "Brian", or "Emma". Default "Female1". |
EndCallMessage | string | No | Message played at the end of the call, after all other messages. |
Options | string | No | Advanced voice options (survey recording, DTMF capture, etc). Contact TNZ for supported values. |
SendTime | string | No | Schedule delivery. Combine with Timezone. |
Timezone | string | No | Windows timezone name for SendTime, e.g. "New Zealand", "AUS Eastern". |
SubAccount | string | No | Sub-account code for billing separation. |
Department | string | No | Department code. |
ChargeCode | string | No | Charge code for billing. |
MessageID | string | No | Supply your own message ID (otherwise auto-generated). |
ReportTo | string | No | Email address to receive delivery reports. |
WebhookCallbackURL | string | No | URL for delivery status callbacks. |
WebhookCallbackFormat | WebhookCallbackFormat | Yes‡ | "JSON", "XML", "POST", or "GET". |
NotificationType | NotificationType | No | "None", "Webhook", or "Email". |
Mode | 'Test' | No | Set 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.
| Field | Type | Description |
|---|---|---|
Tone | number | The DTMF digit this entry responds to (0-9). |
RouteNumber | string | Phone number to route the call to when this key is pressed. |
Play | string | Message read aloud when this key is pressed, instead of or as well as routing. |
PlaySection | string | Where 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.
| Field | Description |
|---|---|
MainPhone | Destination phone number, e.g. "+64211111111". TTS's primary destination field; set by the top-level ToNumber shorthand. |
Recipient | Generic fallback field, set by the bare-string form of AddRecipient("+64211111111"). Read the same as MainPhone. |
ContactID | Addressbook contact reference. Sends to that contact instead of a raw number. |
GroupID | Addressbook group reference. Sends to all members of that group. |
GroupCode | Alternative group lookup by code (instead of GroupID). |
FirstName / LastName / Company / Attention | Personalisation tokens, e.g. [[FirstName]]. |
Custom1-Custom9 | Arbitrary 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
| Value | Behaviour |
|---|---|
NDAS | No detect, always speak - treats every answer as a live person. Default. |
NDAF | No detect, always fax. |
DAS | Detect and speak - plays MessageToAnswerPhones when an answering machine is detected. |
DAF | Detect 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.
| Field | Type | Description |
|---|---|---|
Result | string | "Success" on success; "Failed", "Error", or "Unauthorized" otherwise. |
MessageID | string | The ID of the call you just placed. Present on success only. |
JobNum | string | TNZ's internal job number for this send. Present on success only. |
Status | string | The job's initial status, e.g. "Queued". Present on success only. |
ErrorMessage | string[] | 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
| Parameter | Type | Required | Description |
|---|---|---|---|
TemplateID | string | Yes* | Pre-configured audio template ID, typically built in the Dashboard. |
VoiceFiles | IVoiceFile[] | Yes* | Local audio files to play, mapped onto the named fields below. See the security note in Code samples. |
MessageToPeople | string | Yes* | Played to a person answering the call: spoken text, a pre-encoded base64 WAV/MP3 string, or (via VoiceFiles) audio from a local file. |
MessageToAnswerPhones | string | No | Played if an answering machine picks up instead of a person. Same accepted forms as MessageToPeople. |
Destinations | IVoiceDestination[] | Yes† | One or more destinations, as plain object literals. See Destination fields below. |
ToNumber | string | Yes† | Single-destination shorthand, resolves to MainPhone. Comma-separate multiple numbers, e.g. "+64211111111,+64222222222". |
GroupID | string | Yes† | Single addressbook group shorthand (alternative/addition to Destinations); comma-separated for multiple. |
ContactID | string | Yes† | Single addressbook contact shorthand (alternative/addition to Destinations); comma-separated for multiple. |
AnswerPhoneMode | AnswerPhoneMode | No | How to handle an answering machine: NDAS, NDAF, DAS, or DAF. |
CallerID | string | No | Caller ID shown to the recipient (must be whitelisted under your account). |
RetryAttempts | number | No | Number of retry attempts on no-answer/busy. |
RetryPeriod | number | No | Minutes between retry attempts. |
NumberOfOperators | number | No | Number of simultaneous operators for keypad-routed calls. |
KeypadOptionRequired | boolean | No | Force the caller to press a key before the call proceeds. |
Keypads | IVoiceKeypad[] | No | Keypad menu options. See Keypad fields below. |
CallRouteMessageToPeople | string | No | Played before routing the call to an operator. Same accepted forms as MessageToPeople. |
CallRouteMessageToOperators | string | No | Played to the operator receiving the routed call. Same accepted forms as MessageToPeople. |
CallRouteMessageOnWrongKey | string | No | Played if an invalid key is pressed. Same accepted forms as MessageToPeople. |
EndCallMessage | string | No | Played 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. |
Options | string | No | Advanced call options (survey/DTMF-capture style features). Contact TNZ for supported values. |
Reference | string | No | Your internal reference, returned in reports and webhooks. |
MessageID | string | No | Supply your own message ID (otherwise auto-generated). |
SendTime | string | No | Schedule delivery. Combine with Timezone. |
Timezone | string | No | Windows timezone name for SendTime, e.g. "New Zealand". |
SubAccount | string | No | Sub-account code for billing separation. |
Department | string | No | Department code. |
ChargeCode | string | No | Charge code for billing separation. |
ReportTo | string | No | Email address to receive delivery reports. |
NotificationType | NotificationType | No | Notification delivery mode: None, Webhook, or Email. |
WebhookCallbackURL | string | No | URL for delivery status callbacks. |
WebhookCallbackFormat | WebhookCallbackFormat | Yes‡ | Callback format: JSON, XML, POST, or GET. |
Mode | 'Test' | No | Set 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.
| Field | Type | Description |
|---|---|---|
Tone | number | The DTMF digit this entry responds to (0–9). Required. |
RouteNumber | string | Phone number to route the call to when this key is pressed. |
File | string | Local 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. |
PlayFile | string | Base64-encoded audio played when this key is pressed. Populated automatically from File; can also be supplied directly as a pre-encoded base64 string. |
Play | string | Spoken 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. |
PlaySection | string | Where 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.
| Field | Description |
|---|---|
MainPhone | Destination phone number, e.g. "+64211111111". Voice's primary destination field; the top-level ToNumber shorthand resolves here. |
Recipient | Generic 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 / Attention | Personalisation tokens, e.g. [[FirstName]]. |
Custom1–Custom9 | Arbitrary per-recipient personalisation values, [[Custom1]] … [[Custom9]]. |
ContactID | Addressbook contact reference: sends to that contact instead of a raw number. |
GroupID | Addressbook group reference: sends to all members of that group. |
GroupCode | Alternative 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
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
ErrorMessage | string[] | Present only when Result is not "Success". See Getting Started. |
MessageID | string | The ID of the call you just placed. |
JobNum | string | TNZ's internal job number for this send. |
Status | string | See 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
| Parameter | Type | Required | Description |
|---|---|---|---|
Attachments | string[] | 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. |
TemplateID | string | Yes* | Pre-configured fax template ID (alternative to Attachments). |
Destinations | IFaxDestination[] | Yes† | One or more destinations. See Destination fields below. |
ToNumber | string | Yes† | Single-destination shorthand (alternative to Destinations); comma-separated for multiple. |
GroupID | string | Yes† | Single addressbook group shorthand (alternative/addition to Destinations); comma-separated for multiple. |
ContactID | string | Yes† | Single addressbook contact shorthand (alternative/addition to Destinations); comma-separated for multiple. |
Reference | string | No | Your internal reference, returned in reports and webhooks. |
MessageID | string | No | Supply your own message ID (otherwise auto-generated). |
Resolution | FaxResolution | No | Fax output resolution: FaxResolution.Low or FaxResolution.High. |
CallerID | string | No | Caller ID displayed to the recipient's fax machine. |
CSID | string | No | Called Subscriber ID string shown in the header of the received fax. |
WatermarkFolder | string | No | TNZ watermark folder containing the image/template to stamp onto pages. |
WatermarkFirstPage | string | No | Watermark file stamped onto the first page only. |
WatermarkAllPages | string | No | Watermark file stamped onto every page. |
RetryAttempts | number | No | Number of retry attempts on send failure (busy/no answer/fax error). |
RetryPeriod | number | No | Minutes to wait between retry attempts. |
SendTime | string | No | Schedule delivery. Combine with Timezone. Accepts YYYY-MM-DD, YYYY-MM-DD HH:mm, or ISO 8601. |
Timezone | string | No | Windows timezone name for SendTime, e.g. "New Zealand". |
SubAccount | string | No | Sub-account code for billing separation. |
Department | string | No | Department code. |
ChargeCode | string | No | Charge code for billing. |
ReportTo | string | No | Email address to receive delivery reports. |
NotificationType | NotificationType | No | Notification delivery mode: None, Webhook, or Email. |
WebhookCallbackURL | string | No | URL for delivery status callbacks. |
WebhookCallbackFormat | WebhookCallbackFormat | Yes‡ | Callback format: JSON, XML, POST, or GET. |
Mode | 'Test' | No | Set 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.
| Field | Description |
|---|---|
ToNumber | Destination fax number, e.g. "+6491232345". |
Recipient | Generic destination address, same effect as ToNumber here. This is the shape AddRecipient("+6491111111") produces when passed a bare string. |
ContactID | Addressbook contact reference: sends to that contact instead of a raw number. |
GroupID | Addressbook group reference: sends to all members of that group. |
GroupCode | Alternative group lookup by code (instead of GroupID). |
Attention | Not rendered into a merge tag (Fax has no message body). Still accepted and echoed back in status reports for your own reference. |
Company | Not rendered into a merge tag (Fax has no message body). Still accepted and echoed back in status reports for your own reference. |
FirstName | Not rendered into a merge tag (Fax has no message body). Still accepted and echoed back in status reports for your own reference. |
LastName | Not rendered into a merge tag (Fax has no message body). Still accepted and echoed back in status reports for your own reference. |
Custom1 to Custom9 | Not 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
| Field | Type | Description |
|---|---|---|
Result | "Success" | Always "Success" on this shape. |
MessageID | string | The ID of the fax you just sent. Use this to poll for status. |
JobNum | string | TNZ's internal job number for this send. |
Status | string | Initial job status. |
Error
| Field | Type | Description |
|---|---|---|
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. |
ErrorMessage | string[] | Human-readable error messages describing what failed. Always an array, even when it contains a single entry. |
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
| Parameter | Type | Required | Description |
|---|---|---|---|
Message | string | Yes* | Message body. Supports personalisation tokens [[FirstName]], [[Custom1]], etc. When sent alongside TemplateID, it must match the content of the approved template. |
TemplateID | string | Yes* | Pre-approved WhatsApp template ID. |
FromNumber | string | No | Registered 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. |
Destinations | IWhatsAppDestination[] | Yes† | One or more destinations, as plain object literals. See Destination fields below. |
ToNumber | string | Yes† | Single-destination shorthand. Comma-separate multiple numbers, e.g. "+64211111111,+64221111111". |
GroupID | string | Yes† | Single addressbook group shorthand (alternative/addition to Destinations). |
ContactID | string | Yes† | Single addressbook contact shorthand (alternative/addition to Destinations). |
FallbackMode | WhatsAppFallbackMode | WhatsAppFallbackMode[] | No | Fallback 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". |
Reference | string | No | Your internal reference, returned in reports and webhooks. |
ReportTo | string | No | Email address to receive delivery reports. |
Attachments | string[] | No | Local file paths, read and base64-encoded automatically. See the security note in Code samples below. |
MessageID | string | No | Supply your own message ID (otherwise auto-generated). |
SendTime | string | No | Schedule delivery. Combine with Timezone. |
Timezone | string | No | Windows timezone name for SendTime, e.g. "New Zealand". |
SubAccount | string | No | Sub-account code for billing separation. |
Department | string | No | Department code. |
ChargeCode | string | No | Charge code for billing separation. |
NotificationType | NotificationType | No | Notification delivery mode: None, Webhook, or Email. |
WebhookCallbackURL | string | No | URL for delivery status callbacks. |
WebhookCallbackFormat | WebhookCallbackFormat | Yes‡ | Callback format: JSON, XML, POST, or GET. |
Mode | 'Test' | No | Set 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".
FromNumber is not required client-side
Unlike TNZ's Python library, WhatsApp.SendMessage(...) in this library only checks that at least one of Message or TemplateID is present before making an HTTP call, the same either/or rule SMS uses. FromNumber is accepted and forwarded if you supply it, but its absence does not fail client-side validation. Check response.Result after every send regardless.
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.
| Field | Description |
|---|---|
ToNumber | Destination phone number, e.g. "+64211111111". |
Recipient | Generic destination number, same effect as ToNumber here. Set automatically when AddRecipient(...) is given a bare string. |
FirstName / LastName / Company / Attention | Personalisation tokens, e.g. [[FirstName]]. |
Custom1–Custom9 | Arbitrary per-recipient personalisation values, [[Custom1]] … [[Custom9]]. |
ContactID | Addressbook contact reference: sends to that contact instead of a raw number. |
GroupID | Addressbook group reference: sends to all members of that group. |
GroupCode | Alternative 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
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
ErrorMessage | string[] | Present only when Result is not "Success". See Getting Started. |
MessageID | string | The ID of the message you just sent. |
JobNum | string | TNZ's internal job number for this send. |
Status | string | See 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',
});
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
MessageID | string | The message this status is for. |
JobStatus | string | See Getting Started's Common Response Enums. |
JobNum | string | TNZ's internal job number for this send. |
Account | string | The TNZ account that owns this job. |
SubAccount / Department | string | Echoed from the original send. |
Reference | string | Echoed from the Reference parameter. |
CreatedTimeLocal / CreatedTimeUTC | string | When the job was created. |
DelayedTimeLocal / DelayedTimeUTC | string | The scheduled send time, if SendTime was set. |
Timezone | string | Timezone name used for scheduling. |
Count | number | Total recipients in the job. |
Complete | number | Recipients processed so far. |
Success / Failed | number | Recipients successfully delivered / failed. |
Price | number | Job total cost. |
TotalRecords / RecordsPerPage / PageCount / Page | number | Pagination metadata for Recipients. |
Recipients | RecipientDTO[] | Per-recipient results. See table below. |
ErrorMessage | string[] | See Getting Started. |
Each entry in Recipients
| Field | Description |
|---|---|
Type | See Getting Started's Common Response Enums. |
DestSeq | TNZ's internal sequence ID for this recipient within the job. |
Destination | The recipient's phone number. |
ContactID | Addressbook contact reference, if sent via ContactID/GroupID. |
Status | See Getting Started's Common Response Enums. |
Result | Human-readable delivery result for this recipient. |
SentTimeLocal / SentTimeUTC | When the message was actually sent to this recipient. |
Attention / Company / Custom1–Custom9 | Echoed personalisation fields. See Destination fields above. |
RemoteID | Carrier/network-assigned identifier for this delivery, if available. |
Price | Per-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
Regional Availability
RCS is not supported in New Zealand or Australia. Confirm destination coverage before relying on RCS as a primary channel. Consider Workflow to route messages to another channel where RCS isn't available.
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
| Parameter | Type | Required | Description |
|---|---|---|---|
Message | string | Yes* | Message body. Supports personalisation tokens [[FirstName]], [[Custom1]], etc. |
TemplateID | string | Yes* | Pre-configured message template ID (alternative to Message). |
Destinations | IRCSDestination[] | Yes† | One or more destinations, as plain object literals. See Destination fields below. |
ToNumber | string | Yes† | Single-recipient shorthand, e.g. "+6421000001". Comma-separate multiple numbers, e.g. "+6421000001,+6421000002". |
GroupID | string | Yes† | Single addressbook group shorthand (alternative/addition to Destinations). |
ContactID | string | Yes† | Single addressbook contact shorthand (alternative/addition to Destinations). |
FromNumber | string | No | Sender ID or number, if your account supports multiple. |
FallbackMode | RCSFallbackMode | RCSFallbackMode[] | No | Fallback 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. |
Reference | string | No | Your internal reference, returned in reports and webhooks. |
MessageID | string | No | Supply your own message ID (otherwise auto-generated). |
SendTime | string | No | Schedule delivery. Combine with Timezone. |
Timezone | string | No | Windows Timezone name for SendTime, e.g. "New Zealand", "AUS Eastern". |
SubAccount | string | No | Sub-account code for billing separation. |
Department | string | No | Department code. |
ChargeCode | string | No | Charge code for billing separation. |
ReportTo | string | No | Email address to receive delivery reports. |
WebhookCallbackURL | string | No | URL for delivery status callbacks. |
WebhookCallbackFormat | WebhookCallbackFormat | Yes‡ | Callback format: JSON, XML, POST, or GET. |
NotificationType | NotificationType | No | Notification delivery mode: None, Webhook, or Email. |
Mode | 'Test' | No | Set 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.
| Field | Description |
|---|---|
ToNumber | Recipient phone number in E.164 format (landline or mobile - the SDK does not restrict RCS destinations to mobile numbers), e.g. "+6421000001". |
Recipient | Generic destination number, sent as-is regardless of channel (same effect as ToNumber here). Set automatically when AddRecipient(...) is given a bare string. |
Attention | Personalisation token [[Attention]]. |
FirstName | Personalisation token [[FirstName]]. |
LastName | Personalisation token [[LastName]]. |
Company | Personalisation token [[Company]]. |
Custom1–Custom9 | Arbitrary per-recipient personalisation values, [[Custom1]] … [[Custom9]]. |
ContactID | Addressbook contact reference: sends to that contact instead of a raw number. |
GroupID | Addressbook group reference: sends to all members of that group. |
GroupCode | Alternative 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
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
ErrorMessage | string[] | Present only when Result is not "Success". See Getting Started. This is where both client-side and server-side validation errors surface. |
MessageID | string | The ID of the message you just sent. |
JobNum | string | TNZ's internal job number for this send. |
Status | string | See 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
| Method | Signature | Channels | Returns |
|---|---|---|---|
Status.Poll | Poll({ MessageID, Channel?, RecordsPerPage?, Page? }) | Any channel string, sent straight through (see note below); defaults to "sms" when Channel is omitted. | StatusApiResponseDTO on success. |
SMSReply.Poll | Poll({ MessageID, RecordsPerPage?, Page? }) | SMS only; the request always targets the SMS endpoint. | SMSReplyApiResponseDTO on success. |
SMSReceived.Poll | Poll({ 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(...)
| Field | Type | Description |
|---|---|---|
Result | string | "Success" on success. See Getting Started. |
MessageID | string | The message this status relates to. |
JobStatus | string | The job's current status, e.g. "Pending", "Delayed", "Completed". |
JobNum | string | TNZ's internal job number. |
Account / SubAccount / Department | string | Account and billing-separation fields on the original send. |
Reference | string | Your own reference from the original send, if supplied. |
CreatedTimeLocal / CreatedTimeUTC | string | When the job was created. |
DelayedTimeLocal / DelayedTimeUTC | string | Scheduled send time, if the job was delayed or rescheduled. |
Timezone | string | Timezone the local timestamps above are expressed in. |
Count | number | Total number of recipients on the job. |
Complete | number | Number of recipients processed so far. |
Success | number | Number of recipients delivered successfully. |
Failed | number | Number of recipients that failed. |
Price | number | Total cost of the job. |
TotalRecords | number | Total recipients matching this query, across all pages. |
RecordsPerPage | number | Echoes the request's RecordsPerPage. |
PageCount | number | Total number of pages. |
Page | number | Echoes the request's Page. |
Recipients | RecipientDTO[] | 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:
| Field | Type | Description |
|---|---|---|
Type | string | The message type this recipient relates to, e.g. "SMS". |
DestSeq | number | Sequence number of this destination within the job. |
Destination | string | The destination address, e.g. a mobile number in E.164 format or an email address. |
ContactID | string | Addressbook contact ID, if the destination was sent via one. |
Status | string | This recipient's current status. |
Result | string | Final delivery result for this recipient, e.g. "Delivered", "Bad Number". |
SentTimeLocal / SentTimeUTC | string | When this recipient's message was sent. |
Attention / Company | string | Personalisation fields echoed back from the original send. |
Custom1–Custom9 | string | Custom personalisation fields echoed back from the original send. |
RemoteID | string | Carrier or gateway-level reference for this recipient. |
Price | number | Cost of this recipient's message. |
SMSReplyRecipientDTO adds one field on top of every RecipientDTO field above:
| Field | Type | Description |
|---|---|---|
SMSReplies | SMSReplyRecipientSMSReplyDTO[] | Replies received from this recipient. Empty array if none. |
SMSReplyRecipientSMSReplyDTO fields:
| Field | Type | Description |
|---|---|---|
ReceivedID | string | Unique ID for this reply. |
ReceivedTimeLocal / ReceivedTimeUTC | string | When the reply was received. |
Timezone | string | Timezone the local timestamp is expressed in. |
From | string | The mobile number the reply came from. |
MessageText | string | The 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(...)
| Field | Type | Description |
|---|---|---|
Result | string | "Success" on success. See Getting Started. |
TotalRecords | number | Total messages matching this query, across all pages. |
RecordsPerPage | number | Echoes the request's RecordsPerPage. |
PageCount | number | Total number of pages. |
Page | number | Echoes the request's Page. |
Messages | SMSReceivedDTO[] | Received messages matching the query. |
SMSReceivedDTO fields:
| Field | Type | Description |
|---|---|---|
ReceivedID | string | Unique ID for this received message. |
MessageID | string | If this was matched as a reply, the outbound message's MessageID. |
JobNum | string | TNZ's internal job number for the matched outbound message, if any. |
SubAccount / Department | string | Billing-separation fields. |
ReceivedTimeLocal / ReceivedTimeUTC | string | When the message was received. |
From | string | The mobile number the message came from. |
ContactID | string | Addressbook contact ID, if matched. |
MessageText | string | The message's text content. |
Timezone | string | Timezone the local timestamp is expressed in. |
Failure
| Field | Type | Description |
|---|---|---|
Result | string | "Error", "Failed", or "Unauthorized". See Getting Started. |
ErrorMessage | string[] | 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.
| Channel | Abort | Reschedule | Resubmit | Pacing |
|---|---|---|---|---|
| SMS | ✓ | ✓ | ||
| ✓ | ✓ | ✓ | ||
| TTS | ✓ | ✓ | ✓ | ✓ |
| Voice | ✓ | ✓ | ✓ | ✓ |
| Fax | ✓ | ✓ | ✓ | |
| ✓ | ✓ | |||
| 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
| Method | Signature | Channel validation |
|---|---|---|
Abort.SendRequest | SendRequest({ MessageID, Channel }) | Channel just has to be non-empty; see note below. |
Reschedule.SendRequest | SendRequest({ MessageID, Channel, SendTime }) | Channel just has to be non-empty; see note below. |
Resubmit.SendRequest | SendRequest({ MessageID, Channel, SendTime? }) | Channel must be (case-insensitively) email, fax, tts, or voice. |
Pacing.SendRequest | SendRequest({ 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.
| Field | Type | Description |
|---|---|---|
Result | string | "Success". See Getting Started. |
MessageID | string | The message this action was applied to. |
JobNum | string | TNZ's internal job number this action was applied to. |
Status | string | The job's status after the action, e.g. "Pending", "Delayed", "Completed". |
Action | string | The 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".
| Field | Type | Description |
|---|---|---|
Result | string | "Error", "Failed", or "Unauthorized". See Getting Started. |
ErrorMessage | string[] | 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:
| Value | Description |
|---|---|
WebhookCallbackFormat.JSON | Payload delivered as a JSON-encoded POST body. Used in every example on this page. |
WebhookCallbackFormat.XML | Payload delivered as an XML-encoded POST body. |
WebhookCallbackFormat.POST | Payload delivered as a POST request in TNZ's default (non-JSON, non-XML) encoding. |
WebhookCallbackFormat.GET | Payload 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.
| Field | Type | Description |
|---|---|---|
Version | string | Webhook payload format version. |
Sender | string | Webhook sender address, for correlation. Not a security signature. |
APIKey | string | Webhook token, for correlation. Not a security signature. |
Type | string | On 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". |
Destination | string | The 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. |
ContactID | string | null | Addressbook contact reference, if the destination matched one. |
ReceivedID | string | null | Always null on the delivery result webhook. A unique identifier for the event on the inbound SMS webhook. |
MessageID | string | null | The original outbound message this event relates to, where one is matched. |
SubAccount | string | null | Sub-account code, echoed from the original send. |
Department | string | null | Department code, echoed from the original send. |
JobNumber | string | TNZ's internal job number for the send batch. |
SentTimeLocal / SendTimeUTC / SentTimeUTC_RFC3339 | string | Event 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. |
Status | string | On the delivery result webhook: "Success", "Failed", or "Pending". Always the fixed value "RECEIVED" on the inbound SMS webhook. |
Result | string | On the delivery result webhook: a channel-specific delivery result code. Always the fixed value "RECEIVED" on the inbound SMS webhook. |
Message | string | null | Always null on the delivery result webhook. The reply text on the inbound SMS webhook. |
Price | string | number | null | Cost of the message, before tax and plan credits. See the note below. |
Detail | string | Additional, channel-specific detail, e.g. "SMSParts:2" or "VoiceSeconds:14". |
URL | string | Related 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
Requires Address Book API Access
Your API user needs this permission enabled separately from general API access: Dashboard → Users → API User → API → Address Book API Access.
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
| Field | Type | Description |
|---|---|---|
ViewBy | string | Who can view this contact in the Dashboard: "Account", "SubAccount", "Department", or "No". |
EditBy | string | Who can edit this contact in the Dashboard, same values as ViewBy. |
Attention | string | Personalisation token [[Attention]]. |
Title | string | e.g. "Mr", "Dr". |
Company | string | Personalisation token [[Company]]. |
RecipDepartment | string | The contact's department at their company. Not related to your TNZ Department code. |
FirstName | string | Personalisation token [[FirstName]]. |
LastName | string | Personalisation token [[LastName]]. |
Position | string | Job title. |
StreetAddress / Suburb / City / State / Country / Postcode | string | Postal address fields. |
MainPhone | string | Primary phone number. |
DirectPhone | string | Direct-dial phone number. |
AltPhone1 / AltPhone2 | string | Up to 2 additional phone numbers. |
MobilePhone | string | Mobile number, used as the SMS/WhatsApp/RCS destination when sending via ContactID. |
FaxNumber | string | Fax destination. |
EmailAddress | string | Email 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. |
WebAddress | string | Website URL. |
Custom1-Custom4 | string | Personalisation tokens [[Custom1]]-[[Custom4]]. |
Timezone | string | Timezone 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
| Field | Type | Description |
|---|---|---|
GroupName | string | Display name for the group. |
SubAccount | string | Sub-account code. |
Department | string | Department code. |
ViewEditBy | string | Who 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
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
ErrorMessage | string[] | Present only when Result is not "Success". See Getting Started. |
Contact | ContactModel | The contact record. Present only on success. |
Contact.ContactID | string | The contact's ID. |
Contact.Owner | string | The TNZ user who owns this contact. |
Contact.CreatedTimeLocal / Contact.CreatedTimeUTC | string | When the contact was created, in local time and UTC respectively. |
Contact.UpdatedTimeLocal / Contact.UpdatedTimeUTC | string | When the contact was last updated. |
Contact.Timezone | string | Timezone the local timestamps above are expressed in. |
| every Contact field above | string | Echoed back on Contact, e.g. Contact.FirstName, Contact.EmailAddress, Contact.Custom1-Contact.Custom4. See the Fields table above. |
Contact.List(...) response
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
ErrorMessage | string[] | Present only when Result is not "Success". |
TotalRecords / RecordsPerPage / PageCount / Page | number | Pagination metadata for this page. |
Contacts | ContactModel[] | The matching contacts for this page, each shaped like Contact.Detail(...)'s Contact field. |
Group.Create(...)/Detail(...)/Update(...)/Delete(...) response
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
ErrorMessage | string[] | Present only when Result is not "Success". |
Group | GroupModel | The group record. Present only on success. |
Group.GroupID | string | The group's ID. |
Group.GroupCode | string | Server-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.ViewEditBy | string | Echoed back. See the Fields table above. |
Group.AccessControl | string | "Limited" or "Granted". Read-only. |
Group.Owner | string | The TNZ user who owns this group. |
Group.CreatedTimeLocal / Group.CreatedTimeUTC | string | When the group was created. Unlike Contact, Group has no Updated* timestamps. |
Group.Timezone | string | Timezone the local timestamp above is expressed in. |
Group.List(...) response
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
ErrorMessage | string[] | Present only when Result is not "Success". |
TotalRecords / RecordsPerPage / PageCount / Page | number | Pagination metadata for this page. |
Groups | GroupModel[] | The groups for this page, each shaped like Group.Detail(...)'s Group field. |
ContactGroup.List(...) response
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
ErrorMessage | string[] | Present only when Result is not "Success". |
TotalRecords / RecordsPerPage / PageCount / Page | number | Pagination metadata for this page. |
Contact | ContactModel | The contact whose groups you're listing. |
Groups | GroupModel[] | The groups this contact belongs to, for this page. |
GroupContact.List(...) response
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
ErrorMessage | string[] | Present only when Result is not "Success". |
TotalRecords / RecordsPerPage / PageCount / Page | number | Pagination metadata for this page. |
Group | GroupModel | The group whose members you're listing. |
Contacts | ContactModel[] | The contacts belonging to this group, for this page. |
ContactGroup.Create(...)/Delete(...)/Detail(...) response
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. On Detail(...) this reflects the actual dedicated lookup, not a client-side scan; see the correctness note above. |
ErrorMessage | string[] | Present only when Result is not "Success". |
Contact | ContactModel | The contact side of this relation. |
Group | GroupModel | The group side of this relation. |
GroupContact.Create(...)/Delete(...)/Detail(...) response
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. On Detail(...) this reflects the actual dedicated lookup, not a client-side scan; see the correctness note above. |
ErrorMessage | string[] | Present only when Result is not "Success". |
Group | GroupModel | The group side of this relation. |
Contact | ContactModel | The 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
| Field | Type | Required | Description |
|---|---|---|---|
Destination | string | Yes | The destination to suppress, e.g. "+6421003004" or an email address. |
DestType | string | Yes | The channel this entry applies to. See above. |
ContactID | string | No | Addressbook contact reference to associate with this entry, in addition to Destination. |
SubAccount | string | No | Scope this entry to a sub-account. Leave unset to apply to all sub-accounts. |
Department | string | No | Scope this entry to a department. Leave unset to apply to all departments. |
StopMessage | string | No | The opt-out phrase detected, e.g. "Stop sending me these messages". |
Notes | string | No | Free-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
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
ID | string | This entry's ID. Note the field is plain ID, not OptOutID - see the note above. |
Destination / DestType / ContactID / Department / SubAccount / StopMessage / Notes | string | Echoed back. See the Fields table above. |
OriginalMessage | string | The 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_RFC3339 | string | When the entry was created, in local time, UTC, and RFC3339 UTC respectively. Not settable. |
UpdatedTimeLocal / UpdatedTimeUTC / UpdatedTimeUTC_RFC3339 | string | When the entry was last updated. Not settable. |
Timezone | string | Timezone the local timestamps above are expressed in. |
List(...) response
| Field | Type | Description |
|---|---|---|
Result | string | See Getting Started. |
TotalRecords / RecordsPerPage / PageCount / Page | number | Pagination. |
OptOuts | OptOutApiResponseDTO[] | The matching entries for this page, each shaped like the Detail(...) response fields above. |