.NET Library v3.00

TNZAPI.NET

TNZAPI.NET is TNZ's official .NET library for sending SMS, Email, TTS, Voice, Fax, WhatsApp, RCS, and Workflow messages, and for managing your Addressbook and OptOut list, distributed via NuGet. Drop it into your project 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 C#.

As of v3.00, this library wraps TNZ's JSON REST API (previously an XML API). If you're upgrading from an earlier version, see VERSIONS.md in the repository for the full migration history and TNZ's versioning strategy.

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

Supported .NET Versions

This library supports the following .NET implementations:

  • .NET Standard 2.0 (covers .NET Framework 4.6.1+, Mono, Xamarin, Unity, etc.)
  • .NET6
  • .NET7
  • .NET8
  • .NET9
  • .NET10

Installation

Install the package from NuGet:

dotnet add package TNZAPI.NET

Alternatively, you can download the source code from GitHub and build it yourself. The repository also includes TNZAPI.NET.Samples, a console app covering Addressbook, Messaging, and Webhooks usage, and TNZAPI.NET.Demo, a full API and web demo project.

Continue to Getting Started to create your first TNZApiClient.

Getting Started

Getting started with the TNZ API couldn't be easier. Create a TNZApiClient and you're ready to go.

Register an Account

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

API Credentials

TNZAPI.NET authenticates every request with a JWT Auth Token.

Export your Auth Token

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

Pass it directly to the constructor:

var client = new TNZApiClient("[Your Auth Token]");

or via a TNZApiUser:

var apiUser = new TNZApiUser()
{
    AuthToken = "[Your Auth Token]"
};

var client = new TNZApiClient(apiUser);

Refresh or invalidate your Auth Token

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

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

Every response implements IApiResult: check response.Result == Enums.ResultCode.Success before reading response fields; on failure, response.ErrorMessage is a List<string> of human-readable error messages. Enums.ResultCode has four values: Success, Failed, Unauthorized, RecordNotFound.

if (response.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Success - MessageID: {response.MessageID}");
}
else
{
    foreach (var error in response.ErrorMessage)
    {
        Console.WriteLine($"- Error={error}");
    }
}

Common Response Enums

Every channel's Status(...) and Action (Reschedule/Abort/Resubmit/Pacing) results share these enums, referenced from each channel's own Response tables below rather than repeated in each one:

EnumValuesUsed on
Enums.JobStatusPending, Delayed, Completed, CreditHold, UnknownThe job-level JobStatus/Status field on every *StatusApiResult and *ActionApiResult.
Enums.MessageStatusSuccess, Failed, PendingThe per-recipient Status field on every *RecipientResult.
Enums.RecipientChannelTypeSMS, Email, Voice, Fax, WhatsApp, RCS, UnknownThe per-recipient Type field on every *RecipientResult. SMS recipients report as SMS; both Voice and TTS calls report as Voice (there's no separate TTS value; you already know which endpoint you called).

The Destination Model

Destination is one shared class used by every messaging channel (SMS, Email, Fax, TTS, Voice, WhatsApp, RCS, and Workflow), mirroring the Destinations array shape from the REST API docs field-for-field. Every channel accepts every field below; which address field(s) actually matter depends on the channel (SMS/WhatsApp/RCS read ToNumber/MobilePhone, Fax reads FaxNumber, Email reads EmailAddress, TTS/Voice read MainPhone, and Workflow can read ToNumber, MainPhone, and EmailAddress all at once for omni-channel routing), as shown in each channel's own Destination Fields table.

FieldTypeDescription
Recipientstring?Generic single-value shorthand; the server infers which address type it is from the channel you're sending on. Set by the single-string constructor and named-argument constructor's recipient parameter.
ToNumberstring?Phone destination, read by SMS, WhatsApp, and RCS. Also the destination for Workflow's toNumber shortcut.
MobilePhonestring?Alternative phone destination field alongside ToNumber.
MainPhonestring?Read by TTS and Voice. Also the destination for Workflow's mainPhone shortcut, a separate wire field from ToNumber.
EmailAddressstring?Read by Email. Can also be set on Workflow, alongside ToNumber/MainPhone, for omni-channel Workflow Templates.
FaxNumberstring?Read by Fax.
Companystring?Personalisation token [[Company]].
Attentionstring?Personalisation token [[Attention]].
FirstNamestring?Personalisation token [[FirstName]].
LastNamestring?Personalisation token [[LastName]].
Custom1Custom9string?Personalisation tokens [[Custom1]][[Custom9]].
ContactIDContactID?Send to this addressbook contact instead of a raw destination.
GroupIDGroupID?Send to every member of this addressbook group.
GroupCodestring?Alternative group lookup by code. Object-initializer only; no constructor overload sets it.

Object initializer, with full personalisation

Set any combination of fields at once with new Destination { ... }, including a specific address field like ToNumber directly instead of the generic Recipient shorthand. This is also the only way to set GroupCode, since no constructor overload exposes it.

var destination = new Destination
{
    ToNumber = "+64211111111",
    Company = "Example Company",
    Attention = "Accounts Payable",
    FirstName = "Alice",
    LastName = "Smith",
    Custom1 = "Invoice #1234",
    Custom2 = "Due 2026-08-01",
    Custom3 = "VIP"
};

Named-argument constructor

Destination also has a constructor that takes companyName, attention, firstName, lastName, and custom1custom9 as optional named parameters, useful for a compact one-line call. Its recipient parameter sets only the generic Recipient field, not ToNumber/MobilePhone/MainPhone/FaxNumber/EmailAddress individually; use the object initializer above if you need a specific address field set by name. No addressbook fields (ContactID/GroupID/GroupCode) are settable through this constructor either.

var destination = new Destination(
    recipient: "+64211111111",
    companyName: "Example Company",
    firstName: "Alice",
    custom1: "Invoice #1234",
    custom3: "VIP"
);

Environment Variables

TNZAPI.NET can also be configured via environment variables (useful for CI, containers, or keeping credentials out of source control):

VariablePurposeDefault
TNZ_AUTH_TOKENFallback Auth Token used whenever a TNZApiUser is constructed without one set explicitly. An explicit AuthToken always takes precedence.(none)
TNZ_API_URLOverrides the API host TNZAPI.NET sends requests to. Read fresh on every request, so it can be changed at runtime (e.g. between test cases).https://api.tnz.co.nz
TNZ_ALLOW_INSECURE_HTTPSet to true to allow requests over plain HTTP (useful when pointing TNZ_API_URL at a local/staging server without TLS). By default TNZAPI.NET refuses to send the Auth Token over anything but HTTPS.(unset, HTTPS enforced)
// Picks up TNZ_AUTH_TOKEN automatically since no AuthToken is set explicitly
var client = new TNZApiClient();

Setting environment variables

Windows

Current PowerShell session only:

$env:TNZ_AUTH_TOKEN = "your-auth-token"

Current Command Prompt (cmd.exe) session only:

set TNZ_AUTH_TOKEN=your-auth-token

Permanently, for your user account (visible in new terminals/processes, including Visual Studio, after you open one):

[System.Environment]::SetEnvironmentVariable("TNZ_AUTH_TOKEN", "your-auth-token", "User")

Or via the GUI: Windows Settings → search "Environment Variables" → Edit environment variables for your account.

Linux

Current shell session only:

export TNZ_AUTH_TOKEN="your-auth-token"

Permanently, for your user account: add the export line above to ~/.bashrc (bash), ~/.zshrc (zsh), or ~/.profile, then start a new shell (or source the file).

For a systemd service, set it in the unit file instead:

[Service]
Environment="TNZ_AUTH_TOKEN=your-auth-token"
macOS

Current shell session only:

export TNZ_AUTH_TOKEN="your-auth-token"

Permanently, for your user account: add the export line above to ~/.zshrc (the default shell on modern macOS) or ~/.bash_profile, then start a new terminal (or source the file).

Same pattern applies to TNZ_API_URL and TNZ_ALLOW_INSECURE_HTTP. Just swap the variable name. Note that TNZAPI.NET reads OS-level environment variables only; it does not auto-load a .env file.

Actions Support by Channel

client.Actions is per-messaging-channel, not a single flat facade: each channel exposes only the actions its real API supports. Each channel's own section below shows its supported action(s) with a code sample.

ChannelRescheduleAbortResubmitPacing
SMS
Email
TTS
Voice
Fax
WhatsApp
RCS
Workflow

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 is the only messaging module with no Message/TemplateID text content, no Status, no Received, and no client.Actions.Workflow, just Send.

Quick Example

Workflow is genuinely omni-channel: unlike every other module, toNumber, mainPhone, and emailAddress can all be set together for the same destination. The Workflow Template decides which channel(s) actually get used.

var response = client.Messaging.Workflow.SendMessage(
    workflowTemplateId: "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    toNumber: "+64211111111",
    emailAddress: "test@example.com",
    sendMode: Enums.SendModeType.Test
);

if (response.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Success - MessageID: {response.MessageID}");
}

Parameters (WorkflowModel)

ParameterTypeRequiredDescription
WorkflowTemplateIDstring?YesID of the Workflow Template to trigger (built in the Dashboard).
DestinationsICollection<Destination>?Yes†One or more destinations. See Destination fields below.
ContactIDContactID?NoSingle addressbook contact to send to. The builder and the named-parameter shortcut fold this into Destinations for you; if you construct WorkflowModel directly, setting only ContactID with an empty Destinations fails client-side validation, so populate Destinations too.
GroupIDGroupID?NoSingle addressbook group to send to. Same caveat as ContactID above.
Referencestring?NoYour internal reference, returned in reports and webhooks.
SendTimeDateTime?NoSchedule delivery. Combine with Timezone.
Timezonestring?NoWindows Timezone name for SendTime.
SubAccountstring?NoSub-account code for billing separation.
Departmentstring?NoDepartment code.
ChargeCodestring?NoBilling charge code.
MessageIDMessageID?NoSupply your own message ID (otherwise auto-generated).
WebhookCallbackURLstring?NoURL for delivery status callbacks.
WebhookCallbackFormatEnums.WebhookCallbackType?NoCallback format.
NotificationTypeEnums.NotificationType?NoNotification delivery mode.
SendModeEnums.SendModeTypeNoSet Test to validate without sending. Default Live.

†Set directly via Destinations, via the named SendMessage(...) shortcut's toNumber/mainPhone/emailAddress/contactID/groupID/contactIDs/groupIDs/destination parameters, or via WorkflowBuilder.AddDestination(...)/AddDestinations(...).

Named-parameter shortcut: client.Messaging.Workflow.SendMessage(...) also accepts workflowTemplateId, destination, toNumber, mainPhone, emailAddress, contactID, groupID, contactIDs, groupIDs, destinations, notificationType, sendMode directly. This covers nearly the entire model, since Workflow has no message-content or attachment fields. For SendTime, SubAccount, etc. use WorkflowBuilder or construct a WorkflowModel directly. destination accepts either a plain string or a Destination object directly; anything else throws ArgumentException.

Destination Fields (Destination)

Destination is shared across every channel; see the Destination Model reference in Getting Started for its full field list and construction patterns. Workflow is the one channel where 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.

FieldDescription
RecipientGeneric single-value shorthand; the server infers which address type it is from the channel(s) the Workflow Template routes to.
ToNumberPhone destination, e.g. "+64211111111". Sent as the toNumber shortcut's destination. Can be set alongside MainPhone and EmailAddress on the same Destination for omni-channel Workflow Templates.
MobilePhoneAlternative phone destination field alongside ToNumber.
MainPhonePhone destination sent as the mainPhone shortcut's destination: a separate wire field from ToNumber, letting a Workflow Template distinguish the two. Can be set alongside ToNumber and EmailAddress for omni-channel routing.
EmailAddressEmail destination. Can be set alongside ToNumber/MainPhone on the same Destination for omni-channel Workflow Templates.
FaxNumberFax destination, read if the Workflow Template routes to a Fax channel.
CompanyPersonalisation token [[Company]].
AttentionPersonalisation token [[Attention]].
FirstNamePersonalisation token [[FirstName]].
LastNamePersonalisation token [[LastName]].
Custom1Custom9Personalisation tokens [[Custom1]][[Custom9]], passed through to whichever channel(s) the Workflow Template actually uses.
ContactIDAddressbook contact reference. Sends to that contact instead of raw addresses.
GroupIDAddressbook group reference. Sends to all members of that group.
GroupCodeAlternative group lookup by code.

Code Samples

Single destination, multi-channel shorthand

Trigger a Workflow Template for one destination, setting multiple channel addresses at once. The Template itself decides which channel(s) actually get used.

var response = client.Messaging.Workflow.SendMessage(
    workflowTemplateId: "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    toNumber: "+64211111111",
    emailAddress: "test@example.com",
    sendMode: Enums.SendModeType.Test
);

Via the builder

Use WorkflowBuilder when you need more control than the named-parameter shortcut, e.g. scheduling.

using var builder = new WorkflowBuilder();

var model = builder
    .SetWorkflowTemplateID("a1b2c3d4-e5f6-7890-1234-567890abcdef")
    .AddDestination("+64211111111")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Workflow.SendMessage(model);

Destination with every channel address set

Build a Destination with ToNumber, MainPhone, and EmailAddress all set at once, plus personalisation fields. This is the pattern that makes Workflow genuinely omni-channel: the Template picks whichever destination(s) it needs from the same Destination.

using var builder = new WorkflowBuilder();

var destination = new Destination
{
    ToNumber = "+64211111111",
    MainPhone = "+6491112222",
    EmailAddress = "test@example.com",
    FirstName = "Alice",
    Custom1 = "Account #4432"
};

var model = builder
    .SetWorkflowTemplateID("a1b2c3d4-e5f6-7890-1234-567890abcdef")
    .AddDestination(destination)
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Workflow.SendMessage(model);

Multiple destinations

Trigger the same Workflow Template for more than one destination in a single request.

using var builder = new WorkflowBuilder();

var model = builder
    .SetWorkflowTemplateID("a1b2c3d4-e5f6-7890-1234-567890abcdef")
    .AddDestination("+64211111111")
    .AddDestination("+64222222222")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Workflow.SendMessage(model);

Scheduled send

Delay the trigger to a specific SendTime.

using var builder = new WorkflowBuilder();

var model = builder
    .SetWorkflowTemplateID("a1b2c3d4-e5f6-7890-1234-567890abcdef")
    .AddDestination("+64211111111")
    .SetSendTime(DateTime.Now.AddDays(1))
    .SetTimezone("New Zealand")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Workflow.SendMessage(model);

Response

Workflow has no Status, Received, or Action methods. The Dto folder for this channel contains only one response type.

SendMessage(...)WorkflowApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
MessageIDMessageID?The ID of the Workflow run you just triggered.

SMS

Send text messages to one or more destinations via the TNZ REST API, with optional Voice/RCS/WhatsApp fallback. SMS is more than a one-way notification channel. It's a complete two-way messaging solution: track delivery status in real time (see Poll for Status below), and let recipients reply to keep the conversation going (see Poll for Inbound SMS 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:

  • [[Link:https://example.com/page]]: automatically shortens the URL and tracks click-through engagement (URL Shortener).
  • [[File1]]: inserts a link to the first file attached via the Files parameter ([[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).
  • [[STOP]]: inserts an unsubscribe link that automatically opts the recipient out of future messages (Unsubscribe).
var response = client.Messaging.SMS.SendMessage(
    toNumber: "+64211111111",
    messageText: "View your invoice at [[Link:https://example.com/invoice/123]] or reply [[REPLY]]. Text [[STOP]] to opt out.",
    sendMode: Enums.SendModeType.Test
);

Quick Example

var response = client.Messaging.SMS.SendMessage(
    toNumber: "+64211111111",
    messageText: "Test SMS",
    sendMode: Enums.SendModeType.Test
);

if (response.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Success - MessageID: {response.MessageID}");
}

Parameters (SMSModel)

ParameterTypeRequiredDescription
Messagestring?Yes*Message body. Supports personalisation tokens [[FirstName]], [[Custom1]], etc.
TemplateIDstring?Yes*Pre-configured message template ID (alternative to Message).
DestinationsICollection<Destination>?Yes†One or more destinations. See Destination fields below.
ContactIDContactID?NoSingle addressbook contact to send to (alternative/addition to Destinations).
GroupIDGroupID?NoSingle addressbook group to send to (alternative/addition to Destinations).
Referencestring?NoYour internal reference, returned in reports and webhooks.
FromNumberstring?NoSender ID shown on the recipient's device.
SendTimeDateTime?NoSchedule delivery. Combine with Timezone.
Timezonestring?NoWindows Timezone name for SendTime (e.g. "New Zealand", "AUS Eastern").
SubAccountstring?NoSub-account code for billing separation.
Departmentstring?NoDepartment code.
MessageIDMessageID?NoSupply your own message ID (otherwise auto-generated).
ReportTostring?NoEmail address to receive delivery reports.
WebhookCallbackURLstring?NoURL for delivery status callbacks.
WebhookCallbackFormatEnums.WebhookCallbackType?NoCallback format.
NotificationTypeEnums.NotificationType?NoNotification delivery mode.
FallbackModeICollection<Enums.SMSFallbackMode>?NoFallback channel(s) if SMS fails, tried in the order given, e.g. RCS then Voice.
SMSEmailReplystring?NoEmail address to receive SMS replies.
CharacterConversionboolNoConvert characters outside the GSM character set automatically. Default false.
FilesICollection<Attachment>?NoFiles sent via MessageLink. NZ carriers don't support true MMS; reference the file in your message text with [[File1]] instead, and the recipient gets a link to it.
SendModeEnums.SendModeTypeNoSet Test to validate without sending. Default Live.

*Either Message or TemplateID must be provided.
†Set directly via Destinations, via the named SendMessage(...) shortcut's toNumber/contactID/groupID/contactIDs/groupIDs/destination parameters, or via SMSBuilder.AddDestination(...)/AddDestinations(...).

Named-parameter shortcut: client.Messaging.SMS.SendMessage(...) also accepts a subset of these directly: messageText, reference, destination, toNumber, contactID, groupID, contactIDs, groupIDs, destinations, file, attachments, notificationType, sendMode. For every other field (SendTime, Timezone, SubAccount, FallbackMode, etc.) use SMSBuilder or construct an SMSModel directly. destination accepts either a plain string or a Destination object directly (useful for a single personalised send without the builder); anything else throws ArgumentException.

Destination Fields (Destination)

Destination is shared across every channel; see the Destination Model reference in Getting Started for its full field list and construction patterns. SMS reads ToNumber/MobilePhone as the destination.

FieldDescription
RecipientGeneric single-value shorthand. SMS treats this as the destination phone number when no more specific field is set. Set by the single-string constructor.
ToNumberDestination phone number, e.g. "+64211111111". Read by SMS.
MobilePhoneAlternative phone destination field, also read by SMS.
MainPhoneAccepted but not read by SMS (used by TTS/Voice).
EmailAddressAccepted but not read by SMS (used by Email).
FaxNumberAccepted but not read by SMS (used by Fax).
CompanyPersonalisation token [[Company]].
AttentionPersonalisation token [[Attention]].
FirstNamePersonalisation token [[FirstName]].
LastNamePersonalisation token [[LastName]].
Custom1Custom9Arbitrary per-destination personalisation values, [[Custom1]][[Custom9]].
ContactIDAddressbook contact reference. Sends to that contact instead of a raw number.
GroupIDAddressbook group reference. Sends to all members of that group.
GroupCodeAlternative group lookup by code.

new Destination("+64211111111") sets only the generic Recipient field; the server infers it's a phone number from the SMS channel context. Use the object initializer (new Destination { ToNumber = "...", FirstName = "..." }) when you need a specific address field or personalisation set explicitly.

Code Samples

Single destination shorthand

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

var response = client.Messaging.SMS.SendMessage(
    toNumber: "+64211111111",
    messageText: "Office closed today.",
    sendMode: Enums.SendModeType.Test
);

Multiple destinations, via the builder

Use SMSBuilder when you need multiple destinations, or want to mix raw numbers with existing Addressbook contacts and groups.

var groupID = new GroupID("GGGGGGGG-BBBB-BBBB-CCCC-DDDDDDDDDDDD");
var contactID = new ContactID("CCCCCCCC-BBBB-BBBB-CCCC-DDDDDDDDDDDD");

using var builder = new SMSBuilder();

var model = builder
    .SetMessageText("Test SMS")
    .SetReference("Test SMS - Builder sample")
    .AddDestination("+64211111111")
    .AddDestination("+64222222222")
    .AddDestination(contactID)   // Destination from TNZ Addressbook by ContactID
    .AddDestinations(groupID)    // Destinations from TNZ Addressbook by GroupID
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.SMS.SendMessage(model);

Bulk send to multiple addressbook groups and contacts

Pass lists of existing Addressbook groups and contacts directly to the named-parameter SendMessage(...) shortcut's groupIDs/contactIDs parameters to message everyone in them in one call.

var groupIDs = new List<GroupID>
{
    new GroupID("GGGGGGGG-BBBB-BBBB-CCCC-DDDDDDDDDDDD"),
    new GroupID("GGGGGGGG-BBBB-BBBB-CCCC-EEEEEEEEEEEE")
};

var contactIDs = new List<ContactID>
{
    new ContactID("CCCCCCCC-BBBB-BBBB-CCCC-DDDDDDDDDDDD"),
    new ContactID("CCCCCCCC-BBBB-BBBB-CCCC-EEEEEEEEEEEE")
};

var response = client.Messaging.SMS.SendMessage(
    messageText: "Reminder: your subscription renews tomorrow.",
    groupIDs: groupIDs,
    contactIDs: contactIDs,
    sendMode: Enums.SendModeType.Test
);

Bulk send with per-destination personalisation

Send one message to many destinations while personalising each copy with Destination object initializers instead of plain strings.

using var builder = new SMSBuilder();

var model = builder
    .SetMessageText("Hi [[FirstName]], your appointment is on [[Custom1]].")
    .AddDestination(new Destination
    {
        ToNumber = "+64211111111",
        FirstName = "Alice",
        Custom1 = "Monday 3pm"
    })
    .AddDestination(new Destination
    {
        ToNumber = "+64222222222",
        FirstName = "Bob",
        Custom1 = "Tuesday 10am"
    })
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.SMS.SendMessage(model);

Send a file via MessageLink

NZ carriers don't support MMS. Attach a file with SMSBuilder.AddAttachment(...) and reference it in the message text with [[File1]]; the recipient gets an SMS with a link to the file instead of an inline attachment.

using var builder = new SMSBuilder();

var model = builder
    .SetMessageText("Here's the photo you requested: [[File1]]")
    .AddDestination("+64211111111")
    .AddAttachment(@"C:\files\photo.jpg")   // reads and base64-encodes the file
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.SMS.SendMessage(model);

Scheduled send with webhook callback

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

using var builder = new SMSBuilder();

var model = builder
    .SetMessageText("Your reminder.")
    .AddDestination("+64211111111")
    .SetSendTime(DateTime.Now.AddDays(1))
    .SetTimezone("New Zealand")
    .SetWebhookCallbackURL("https://yourapp.example.com/webhooks/sms")
    .SetWebhookCallbackFormat(Enums.WebhookCallbackType.JSON)
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.SMS.SendMessage(model);

Poll for status

Check delivery progress and per-recipient results any time after sending.

var status = client.Messaging.SMS.Status(response.MessageID);

if (status.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"JobStatus: {status.JobStatus}");
    foreach (var recipient in status.Recipients)
    {
        Console.WriteLine($" -> {recipient.Destination}: {recipient.Status} ({recipient.Result})");

        foreach (var reply in recipient.SMSReplies)
        {
            Console.WriteLine($"    reply: {reply.MessageText}");
        }
    }
}

Poll for inbound SMS

Retrieve SMS replies received in the last timePeriod minutes, as an alternative to configuring a webhook.

var received = client.Messaging.SMS.Received(timePeriod: 1440); // minutes

if (received.Result == Enums.ResultCode.Success)
{
    foreach (var message in received.Messages)
    {
        Console.WriteLine($"From {message.From}: {message.MessageText}");
    }
}

Poll for replies to a specific message

Retrieve replies to one outbound MessageID, as an alternative to scanning all inbound SMS with Received(...).

var replies = client.Messaging.SMS.SMSReply(response.MessageID);

if (replies.Result == Enums.ResultCode.Success)
{
    foreach (var recipient in replies.Recipients)
    {
        foreach (var reply in recipient.SMSReplies)
        {
            Console.WriteLine($"{recipient.Destination} replied: {reply.MessageText}");
        }
    }
}

Actions

SMS supports Reschedule and Abort (see the Getting Started capability table for all channels).

Reschedule

Move a not-yet-sent message to a new sendTime.

var response = client.Actions.SMS.Reschedule(
    messageID: response.MessageID,
    sendTime: DateTime.Parse("2026-12-31T12:00:00")
);

if (response.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Action: {response.Action}, Status: {response.Status}");
}

Abort

Cancel a scheduled message before it sends.

client.Actions.SMS.Abort(response.MessageID);

Response

SendMessage(...)SMSApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
MessageIDMessageID?The ID of the message you just sent.

Status(...)SMSStatusApiResult

FieldTypeDescription
MessageIDMessageID?The message this status is for.
JobStatusEnums.JobStatusSee Getting Started's Common Response Enums.
JobNumstring?TNZ's internal job number for this send.
Accountstring?The TNZ account that owns this job.
SubAccount / Departmentstring?Echoed from the original send.
Referencestring?Echoed from the Reference parameter.
CreatedTimeLocal / CreatedTimeUTCDateTime?When the job was created, in local time and UTC.
DelayedTimeLocal / DelayedTimeUTCDateTime?The scheduled send time, if SendTime was set.
Timezonestring?Windows Timezone name used for scheduling.
CountintTotal recipients in the job.
CompleteintRecipients processed so far.
Success / FailedintRecipients successfully delivered / failed.
Pricedecimal?Job total cost.
TotalRecords / RecordsPerPage / PageCount / PageintPagination metadata for Recipients.
RecipientsList<SMSRecipientResult>Per-recipient results. See table below.
SMSRecipientResult (each entry in Recipients)
FieldTypeDescription
TypeEnums.RecipientChannelTypeSee Getting Started's Common Response Enums.
DestSeqstring?TNZ's internal sequence ID for this recipient within the job.
Destinationstring?The recipient's phone number.
ContactIDContactID?Addressbook contact reference, if sent via ContactID/GroupID.
StatusEnums.MessageStatusSee Getting Started's Common Response Enums.
Resultstring?Human-readable delivery result for this recipient.
SentTimeLocal / SentTimeUTCDateTime?When the message was actually sent to this recipient.
Attention / Company / Custom1Custom9string?Echoed personalisation fields. See Destination Fields above.
RemoteIDstring?Carrier/network-assigned identifier for this delivery, if available.
Pricedecimal?Per-recipient cost.
SMSRepliesList<SMSReplyResult>Inbound replies from this recipient. See table below.
SMSReplyResult (each entry in SMSReplies)
FieldTypeDescription
ReceivedIDReceivedID?Unique identifier for this reply.
ReceivedTimeLocal / ReceivedTimeUTCDateTime?When the reply was received.
Timezonestring?Windows Timezone name for ReceivedTimeLocal.
Fromstring?The replying phone number.
MessageTextstring?The reply body.

SMSReply(...)SMSStatusApiResult

Same result type as Status(...) above, scoped to replies received against the given MessageID. Reply text is in each entry's Recipients[].SMSReplies[].MessageText. TotalRecords/RecordsPerPage/PageCount/Page paginate the Recipients list, controlled by SMSReply(...)'s recordsPerPage/page parameters.

Received(...)SMSReceivedApiResult

FieldTypeDescription
TotalRecords / RecordsPerPage / PageCount / PageintPagination metadata for Messages.
MessagesList<SMSReceivedMessage>Inbound SMS messages. See table below.
SMSReceivedMessage (each entry in Messages)
FieldTypeDescription
ReceivedIDReceivedID?Unique identifier for this message.
MessageIDMessageID?The original outbound message this replies to, if determinable.
JobNumstring?The original send job's number, if applicable.
SubAccount / Departmentstring?Echoed billing codes from the original send.
ReceivedTimeLocal / ReceivedTimeUTC / ReceivedTimeUTC_RFC3339DateTime?When the message was received, in three formats.
Fromstring?The sender's phone number.
ContactIDContactID?Addressbook contact reference, if the sender matched one.
MessageTextstring?The message body.
Timezonestring?Windows Timezone name for ReceivedTimeLocal.
Versionstring?Payload format version.

Reschedule(...)/Abort(...)SMSActionApiResult

FieldTypeDescription
ActionResultstring?Human-readable result of the action.
MessageIDMessageID?The message this action was applied to.
JobNumstring?The job number this action was applied to.
StatusEnums.JobStatusSee Getting Started's Common Response Enums.
Actionstring?The action performed, e.g. "Reschedule".

Email

Go beyond plain text with TNZ's flexible Email API. Send beautifully crafted HTML emails using your own content, or specify a TemplateID and let your team manage their own HTML designs using the Dashboard's WYSIWYG editor, with no code changes needed when the design updates. Attach files, and TNZ automatically tracks link clicks in the message body. Whether you're sending invoices, newsletters, or critical alerts, monitor delivery status every step of the way.

Quick Example

var response = 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",
    sendMode: Enums.SendModeType.Test
);

if (response.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Success - MessageID: {response.MessageID}");
}

Parameters (EmailModel)

ParameterTypeRequiredDescription
MessagePlainstring?Yes*Plain-text body.
MessageHTMLstring?Yes*HTML body.
TemplateIDstring?Yes*Pre-configured message template ID (alternative to MessagePlain/MessageHTML).
EmailSubjectstring?YesEmail subject line.
DestinationsICollection<Destination>?Yes†One or more destinations. See Destination fields below.
ContactIDContactID?NoSingle addressbook contact to send to (alternative/addition to Destinations).
GroupIDGroupID?NoSingle addressbook group to send to (alternative/addition to Destinations).
Referencestring?NoYour internal reference, returned in reports and webhooks.
FromEmailstring?NoSender address. Leave blank to use your API username.
Fromstring?NoSender display name.
SMTPFromstring?NoSMTP envelope-from override.
CCEmailstring?NoCC address.
ReplyTostring?NoReply-To address.
SendTimeDateTime?NoSchedule delivery. Combine with Timezone.
Timezonestring?NoWindows Timezone name for SendTime.
SubAccountstring?NoSub-account code for billing separation.
Departmentstring?NoDepartment code.
MessageIDMessageID?NoSupply your own message ID (otherwise auto-generated).
ReportTostring?NoEmail address to receive delivery reports.
WebhookCallbackURLstring?NoURL for delivery status callbacks.
WebhookCallbackFormatEnums.WebhookCallbackType?NoCallback format.
NotificationTypeEnums.NotificationType?NoNotification delivery mode.
FilesICollection<Attachment>?NoEmail attachments.
SendModeEnums.SendModeTypeNoSet Test to validate without sending. Default Live.

*Either MessagePlain, MessageHTML, or TemplateID must be provided; HTML and plain-text may be combined for multi-part email.
†Set directly via Destinations, via the named SendMessage(...) shortcut's emailAddress/contactID/groupID/contactIDs/groupIDs/destination parameters, or via EmailBuilder.AddDestination(...)/AddDestinations(...).

Named-parameter shortcut: client.Messaging.Email.SendMessage(...) also accepts messagePlain, messageHTML, destination, emailAddress, contactID, groupID, contactIDs, groupIDs, destinations, emailSubject, fromEmail, file, attachments, notificationType, sendMode directly. For CCEmail, ReplyTo, SendTime, SubAccount, etc. use EmailBuilder or construct an EmailModel directly. destination accepts either a plain string or a Destination object directly; anything else throws ArgumentException.

Destination Fields (Destination)

Destination is shared across every channel; see the Destination Model reference in Getting Started for its full field list and construction patterns. Email reads EmailAddress as the destination.

FieldDescription
RecipientGeneric single-value shorthand; sets the generic Recipient field, not EmailAddress directly.
ToNumberAccepted but not read by Email.
MobilePhoneAccepted but not read by Email.
MainPhoneAccepted but not read by Email.
EmailAddressDestination email address, read by Email as the destination, e.g. "email.one@test.com".
FaxNumberAccepted but not read by Email.
CompanyPersonalisation token [[Company]].
AttentionPersonalisation token [[Attention]].
FirstNamePersonalisation token [[FirstName]].
LastNamePersonalisation token [[LastName]].
Custom1Custom9Arbitrary per-destination personalisation values, [[Custom1]][[Custom9]].
ContactIDAddressbook contact reference. Sends to that contact instead of a raw address.
GroupIDAddressbook group reference. Sends to all members of that group.
GroupCodeAlternative group lookup by code.

Code Samples

Single destination, plain text

The simplest way to send: pass a plain-text body and destination directly to SendMessage(...).

var response = client.Messaging.Email.SendMessage(
    fromEmail: "from@test.com",
    emailSubject: "Test Email",
    messagePlain: "Test Email Body",
    emailAddress: "email.one@test.com",
    sendMode: Enums.SendModeType.Test
);

Via the builder

Use EmailBuilder when you need fields the named-parameter shortcut doesn't cover, e.g. CCEmail, ReplyTo, or scheduling.

using var builder = new EmailBuilder();

var model = builder
    .SetEmailSubject("Test Email")
    .SetMessagePlain("Test Email Body")
    .AddDestination("email.one@test.com")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Email.SendMessage(model);

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:

using var builder = new EmailBuilder();

var 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>";

var model = builder
    .SetEmailSubject("Your order has shipped")
    .SetMessageHTML(htmlBody)
    .AddDestination(new Destination
    {
        EmailAddress = "email.one@test.com",
        FirstName = "Alice"
    })
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Email.SendMessage(model);

With an attachment

Attach a file using its base64-encoded content.

using var builder = new EmailBuilder();

var model = builder
    .SetEmailSubject("Test Email")
    .SetMessagePlain("See attached.")
    .AddDestination("email.one@test.com")
    .AddAttachment("My Document.pdf", "[base64-encoded-file-data]")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Email.SendMessage(model);

Custom sender, reply-to, and CC

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

using var builder = new EmailBuilder();

var model = builder
    .SetEmailSubject("Your invoice is ready")
    .SetMessagePlain("Please find your invoice attached.")
    .SetSMTPFrom("billing@test.com")
    .SetFrom("Test Company Billing")
    .SetFromEmail("billing@test.com")
    .SetReplyTo("accounts@test.com")
    .SetCCEmail("manager@test.com")
    .AddDestination("email.one@test.com")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Email.SendMessage(model);

Bulk send to multiple addressbook groups and contacts

Pass lists of existing Addressbook groups and contacts directly to the named-parameter SendMessage(...) shortcut's groupIDs/contactIDs parameters to message everyone in them in one call.

var groupIDs = new List<GroupID>
{
    new GroupID("GGGGGGGG-BBBB-BBBB-CCCC-DDDDDDDDDDDD"),
    new GroupID("GGGGGGGG-BBBB-BBBB-CCCC-EEEEEEEEEEEE")
};

var contactIDs = new List<ContactID>
{
    new ContactID("CCCCCCCC-BBBB-BBBB-CCCC-DDDDDDDDDDDD"),
    new ContactID("CCCCCCCC-BBBB-BBBB-CCCC-EEEEEEEEEEEE")
};

var response = client.Messaging.Email.SendMessage(
    emailSubject: "Test Email",
    messagePlain: "Test Email Body",
    groupIDs: groupIDs,
    contactIDs: contactIDs,
    sendMode: Enums.SendModeType.Test
);

Poll for status

Check delivery progress and per-recipient results any time after sending.

var status = client.Messaging.Email.Status(response.MessageID);

if (status.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"JobStatus: '{status.JobStatus}', JobNum: '{status.JobNum}'");

    foreach (var recipient in status.Recipients)
    {
        Console.WriteLine($" -> {recipient.Destination}: {recipient.Status} ({recipient.Result})");
    }
}

Actions

Email supports Reschedule, Abort, and Resubmit (see the Getting Started capability table for all channels).

Reschedule

Move a not-yet-sent email to a new sendTime.

var response = client.Actions.Email.Reschedule(
    messageID: response.MessageID,
    sendTime: DateTime.Parse("2026-12-31T12:00:00")
);

if (response.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Action: {response.Action}, Status: {response.Status}");
}

Abort

Cancel a scheduled email before it sends.

client.Actions.Email.Abort(response.MessageID);

Resubmit

Resubmit a message for delivery at a new scheduled time, without rebuilding the whole request.

client.Actions.Email.Resubmit(response.MessageID, DateTime.Now.AddHours(1));

Response

SendMessage(...)EmailApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
MessageIDMessageID?The ID of the message you just sent.

Status(...)EmailStatusApiResult

FieldTypeDescription
MessageIDMessageID?The message this status is for.
JobStatusEnums.JobStatusSee Getting Started's Common Response Enums.
JobNumstring?TNZ's internal job number for this send.
Accountstring?The TNZ account that owns this job.
SubAccount / Departmentstring?Echoed from the original send.
Referencestring?Echoed from the Reference parameter.
CreatedTimeLocal / CreatedTimeUTCDateTime?When the job was created, in local time and UTC.
DelayedTimeLocal / DelayedTimeUTCDateTime?The scheduled send time, if SendTime was set.
Timezonestring?Windows Timezone name used for scheduling.
CountintTotal recipients in the job.
CompleteintRecipients processed so far.
Success / FailedintRecipients successfully delivered / failed.
Pricedecimal?Job total cost.
TotalRecords / RecordsPerPage / PageCount / PageintPagination metadata for Recipients.
RecipientsList<EmailRecipientResult>Per-recipient results. See table below.
EmailRecipientResult (each entry in Recipients)
FieldTypeDescription
TypeEnums.RecipientChannelTypeSee Getting Started's Common Response Enums.
DestSeqstring?TNZ's internal sequence ID for this recipient within the job.
Destinationstring?The recipient's email address.
ContactIDContactID?Addressbook contact reference, if sent via ContactID/GroupID.
StatusEnums.MessageStatusSee Getting Started's Common Response Enums.
Resultstring?Human-readable delivery result for this recipient.
SentTimeLocal / SentTimeUTCDateTime?When the message was actually sent to this recipient.
Attention / Company / Custom1Custom9string?Echoed personalisation fields. See Destination Fields above.
RemoteIDstring?Carrier/network-assigned identifier for this delivery, if available.
Pricedecimal?Per-recipient cost.

Reschedule(...)/Abort(...)/Resubmit(...)EmailActionApiResult

FieldTypeDescription
ActionResultstring?Human-readable result of the action.
MessageIDMessageID?The message this action was applied to.
JobNumstring?The job number this action was applied to.
StatusEnums.JobStatusSee Getting Started's Common Response Enums.
Actionstring?The action performed, e.g. "Resubmit".

TTS (Text-to-Speech)

Transform written text into clear, natural-sounding voice calls. Ideal 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 deliver an alternate message when a voicemail picks up instead of a person.

Quick Example

var response = client.Messaging.TTS.SendMessage(
    messageToPeople: "Hello, this is a call from test. This is relevant information.",
    toNumber: "+64211111111",
    sendMode: Enums.SendModeType.Test
);

if (response.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Success - MessageID: {response.MessageID}");
}

Parameters (TTSModel)

ParameterTypeRequiredDescription
MessageToPeoplestring?Yes*Text read aloud to a human answering the call.
TemplateIDstring?Yes*Pre-configured message template ID (alternative to MessageToPeople).
DestinationsICollection<Destination>?Yes†One or more destinations. See Destination fields below.
ContactIDContactID?NoSingle addressbook contact to send to (alternative/addition to Destinations).
GroupIDGroupID?NoSingle addressbook group to send to (alternative/addition to Destinations).
Referencestring?NoYour internal reference, returned in reports and webhooks.
MessageToAnswerPhonesstring?NoAlternative text read when an answering machine picks up.
AnswerPhoneModeEnums.AnswerPhoneMode?NoHow to handle an answering machine.
KeypadsICollection<KeypadModel>?NoKeypad menu options. See Keypad fields below.
KeypadOptionRequiredboolNoForce the caller to press a key before the call proceeds. Default false.
CallRouteMessageOnWrongKeystring?NoMessage played if an invalid key is pressed.
EndCallMessagestring?NoMessage played just before the call ends.
CallRouteMessageToPeoplestring?NoMessage played before routing to an operator.
CallRouteMessageToOperatorsstring?NoMessage played to the operator receiving the routed call.
NumberOfOperatorsint?NoNumber of simultaneous operators for keypad-routed calls.
RetryAttemptsint?NoNumber of retry attempts on no-answer/busy.
RetryPeriodint?NoMinutes between retry attempts.
CallerIDstring?NoCaller ID shown to the recipient.
Voicestring?NoTTS voice to use.
Optionsstring?NoAdditional provider-specific options.
SendTimeDateTime?NoSchedule delivery. Combine with Timezone.
Timezonestring?NoWindows Timezone name for SendTime.
SubAccountstring?NoSub-account code for billing separation.
Departmentstring?NoDepartment code.
ChargeCodestring?NoBilling charge code.
MessageIDMessageID?NoSupply your own message ID (otherwise auto-generated).
ReportTostring?NoEmail address to receive delivery reports.
WebhookCallbackURLstring?NoURL for delivery status callbacks.
WebhookCallbackFormatEnums.WebhookCallbackType?NoCallback format.
NotificationTypeEnums.NotificationType?NoNotification delivery mode.
SendModeEnums.SendModeTypeNoSet Test to validate without sending. Default Live.

*Either MessageToPeople or TemplateID must be provided.
†Set directly via Destinations, via the named SendMessage(...) shortcut's toNumber/contactID/groupID/contactIDs/groupIDs/destination parameters, or via TTSBuilder.AddDestination(...)/AddDestinations(...).

Named-parameter shortcut: client.Messaging.TTS.SendMessage(...) also accepts messageToPeople, destination, toNumber, contactID, groupID, contactIDs, groupIDs, destinations, notificationType, sendMode directly. TTS has no SendMessage(...) shortcut for keypads, attachments, or any other field above. Use TTSBuilder or construct a TTSModel directly. destination accepts either a plain string or a Destination object directly; anything else throws ArgumentException.

Keypad Fields (KeypadModel)

FieldTypeDescription
ToneintThe DTMF digit this entry responds to.
Playstring?Message played when this key is pressed.
RouteNumberstring?Phone number to route the call to when this key is pressed.
PlaySectionEnums.KeypadPlaySection?Where in the call flow this keypad applies (e.g. Main).

Destination Fields (Destination)

Destination is shared across every channel; see the Destination Model reference in Getting Started for its full field list and construction patterns. TTS reads MainPhone for its destination; other address fields (ToNumber, MobilePhone, EmailAddress, FaxNumber) are accepted but not read by TTS.

FieldDescription
RecipientGeneric single-value shorthand; the server infers which address type it is from the channel you're sending on.
ToNumberAccepted but not read by TTS.
MobilePhoneAccepted but not read by TTS.
MainPhoneDestination phone number, e.g. "+64211111111". TTS reads MainPhone for its destination.
EmailAddressAccepted but not read by TTS.
FaxNumberAccepted but not read by TTS.
ContactIDAddressbook contact reference. Sends to that contact instead of a raw number.
GroupIDAddressbook group reference. Sends to all members of that group.
GroupCodeAlternative group lookup by code.
CompanyPersonalisation token [[Company]].
AttentionPersonalisation token [[Attention]].
FirstNamePersonalisation token [[FirstName]].
LastNamePersonalisation token [[LastName]].
Custom1Custom9Arbitrary per-destination personalisation values, [[Custom1]][[Custom9]].

Code Samples

Single destination shorthand

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

var response = client.Messaging.TTS.SendMessage(
    messageToPeople: "Hello, this is a call from test. This is relevant information.",
    toNumber: "+64211111111",
    sendMode: Enums.SendModeType.Test
);

With a keypad menu, via the builder

Add interactive keypad options so callers can route themselves to a different number by pressing a key.

using var builder = new TTSBuilder();

var model = builder
    .SetMessageToPeople("Press 1 for sales, press 2 for support.")
    .AddDestination("+64211111111")
    .AddKeypad(new KeypadModel
    {
        Tone = 1,
        Play = "Connecting you to sales now.",
        PlaySection = Enums.KeypadPlaySection.Main,
        RouteNumber = "+64211112222"
    })
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.TTS.SendMessage(model);

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.

using var builder = new TTSBuilder();

var model = builder
    .SetMessageToPeople("Press 1 for sales, press 2 for support, press 3 to hear our address, or press 9 to hear this message again.")
    .AddDestination("+64211111111")
    .AddKeypad(new KeypadModel
    {
        Tone = 1,
        RouteNumber = "+64211112222"
    })
    .AddKeypad(new KeypadModel
    {
        Tone = 2,
        RouteNumber = "+64211113333"
    })
    .AddKeypad(new KeypadModel
    {
        Tone = 3,
        Play = "We're located at 123 Example Street, Auckland."
    })
    .AddKeypad(new KeypadModel
    {
        Tone = 9,
        PlaySection = Enums.KeypadPlaySection.Main
    })
    .SetKeypadOptionRequired(true)
    .SetCallRouteMessageOnWrongKey("Sorry, that key isn't recognised. Please try again.")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.TTS.SendMessage(model);

Multiple destinations

Call more than one number in a single request using TTSBuilder.

using var builder = new TTSBuilder();

var model = builder
    .SetMessageToPeople("Hello, this is a call from test.")
    .AddDestination("+64211111111")
    .AddDestination("+64222222222")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.TTS.SendMessage(model);

With personalisation

Build a Destination with MainPhone and personalisation fields set, for use with [[FirstName]]-style tokens in MessageToPeople.

using var builder = new TTSBuilder();

var model = builder
    .SetMessageToPeople("Hello [[FirstName]], this is a reminder about your appointment on [[Custom1]].")
    .AddDestination(new Destination
    {
        MainPhone = "+64211111111",
        FirstName = "Alice",
        Custom1 = "Monday 3pm"
    })
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.TTS.SendMessage(model);

Scheduled send

Delay the call to a specific SendTime.

using var builder = new TTSBuilder();

var model = builder
    .SetMessageToPeople("Your reminder call.")
    .AddDestination("+64211111111")
    .SetSendTime(DateTime.Now.AddDays(1))
    .SetTimezone("New Zealand")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.TTS.SendMessage(model);

Retry attempts, caller ID, and reporting

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.

using var builder = new TTSBuilder();

var model = builder
    .SetMessageToPeople("Hello, this is a call from test. This is relevant information.")
    .AddDestination("+64211111111")
    .SetCallerID("+6499999999")
    .SetVoice("Female1")
    .SetNumberOfOperators(1)
    .SetRetryAttempts(3)
    .SetRetryPeriod(1)
    .SetReportTo("report@example.com")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.TTS.SendMessage(model);

Poll for status

Check call progress and per-recipient results any time after sending.

var status = client.Messaging.TTS.Status(response.MessageID);

if (status.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"JobStatus: '{status.JobStatus}', JobNum: '{status.JobNum}'");

    foreach (var recipient in status.Recipients)
    {
        Console.WriteLine($" -> {recipient.Destination}: {recipient.Status} ({recipient.Result})");
    }
}

Actions

TTS supports Reschedule, Abort, Resubmit, and Pacing (see the Getting Started capability table for all channels).

Reschedule

Move a not-yet-placed call to a new sendTime.

var response = client.Actions.TTS.Reschedule(
    messageID: response.MessageID,
    sendTime: DateTime.Parse("2026-12-31T12:00:00")
);

Abort

Cancel a scheduled call before it's placed.

client.Actions.TTS.Abort(response.MessageID);

Resubmit

Re-send a call at a new scheduled time without rebuilding the whole request.

client.Actions.TTS.Resubmit(response.MessageID, DateTime.Now.AddHours(1));

Pacing (adjust simultaneous operators)

Change how many calls run at once on an in-progress keypad-routed job.

client.Actions.TTS.Pacing(response.MessageID, numberOfOperators: 1);

Response

SendMessage(...)TTSApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
MessageIDMessageID?The ID of the call you just placed.

Status(...)TTSStatusApiResult

FieldTypeDescription
MessageIDMessageID?The call this status is for.
JobStatusEnums.JobStatusSee Getting Started's Common Response Enums.
JobNumstring?TNZ's internal job number for this send.
Accountstring?The TNZ account that owns this job.
SubAccount / Departmentstring?Echoed from the original send.
Referencestring?Echoed from the Reference parameter.
CreatedTimeLocal / CreatedTimeUTCDateTime?When the job was created, in local time and UTC.
DelayedTimeLocal / DelayedTimeUTCDateTime?The scheduled send time, if SendTime was set.
Timezonestring?Windows Timezone name used for scheduling.
CountintTotal recipients in the job.
CompleteintRecipients processed so far.
Success / FailedintCalls successfully completed / failed.
Pricedecimal?Job total cost.
TotalRecords / RecordsPerPage / PageCount / PageintPagination metadata for Recipients.
RecipientsList<TTSRecipientResult>Per-recipient results. See table below.
TTSRecipientResult (each entry in Recipients)
FieldTypeDescription
TypeEnums.RecipientChannelTypeSee Getting Started's Common Response Enums. TTS calls report as Voice.
DestSeqstring?TNZ's internal sequence ID for this recipient within the job.
Destinationstring?The recipient's phone number.
ContactIDContactID?Addressbook contact reference, if sent via ContactID/GroupID.
StatusEnums.MessageStatusSee Getting Started's Common Response Enums.
Resultstring?Human-readable call result for this recipient.
SentTimeLocal / SentTimeUTCDateTime?When the call was actually placed to this recipient.
Attention / Company / Custom1Custom9string?Echoed personalisation fields. See Destination Fields above.
RemoteIDstring?Carrier/network-assigned identifier for this call, if available.
Pricedecimal?Per-recipient cost.

Reschedule(...)/Abort(...)/Resubmit(...)/Pacing(...)TTSActionApiResult

FieldTypeDescription
ActionResultstring?Human-readable result of the action.
MessageIDMessageID?The call this action was applied to.
JobNumstring?The job number this action was applied to.
StatusEnums.JobStatusSee Getting Started's Common Response Enums.
Actionstring?The action performed, e.g. "Pacing".

Voice (Pre-Recorded Audio)

Deliver impactful messages by sending pre-recorded audio files as voice calls. Useful for alerts where a human voice adds a personal touch, or for conveying complex information that's easier to hear than read. Manage calls to individuals or large groups, with retry attempts for failed calls (RetryAttempts/RetryPeriod), answering machine detection, and interactive keypad responses.

Voice shares its Keypad model/enums with TTS (KeypadModel, Enums.AnswerPhoneMode, Enums.KeypadPlaySection). The builder API is identical.

Quick Example

var response = client.Messaging.Voice.SendMessage(
    toNumber: "+64211111111",
    sendMode: Enums.SendModeType.Test
);

if (response.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Success - MessageID: {response.MessageID}");
}

Parameters (VoiceModel)

ParameterTypeRequiredDescription
TemplateIDstring?Yes*Pre-configured audio template ID.
MessageToPeoplestring?NoPresent for parity with TTSModel. A plain Voice send typically relies on TemplateID for its pre-recorded audio instead.
DestinationsICollection<Destination>?Yes†One or more destinations. See Destination fields below.
ContactIDContactID?NoSingle addressbook contact to send to (alternative/addition to Destinations).
GroupIDGroupID?NoSingle addressbook group to send to (alternative/addition to Destinations).
Referencestring?NoYour internal reference, returned in reports and webhooks.
MessageToAnswerPhonesstring?NoAlternative audio played when an answering machine picks up.
AnswerPhoneModeEnums.AnswerPhoneMode?NoHow to handle an answering machine.
KeypadsICollection<KeypadModel>?NoKeypad menu options, same shape as TTS's. See Keypad fields below.
VoiceFilesICollection<VoiceFileModel>?NoNamed audio clips (Name + base64-encoded File), added via VoiceBuilder.AddVoiceFile(name, file). TNZ hasn't published how these interact with keypad-triggered playback; use Keypads' own PlayFile/File pair (above) for that.
KeypadOptionRequiredboolNoForce the caller to press a key before the call proceeds. Default false.
CallRouteMessageOnWrongKeystring?NoMessage played if an invalid key is pressed.
CallRouteMessageToPeoplestring?NoMessage played before routing to an operator.
CallRouteMessageToOperatorsstring?NoMessage played to the operator receiving the routed call.
EndCallMessagestring?NoMessage played at the end of the call, after any keypad routing.
NumberOfOperatorsint?NoNumber of simultaneous operators for keypad-routed calls.
RetryAttemptsint?NoNumber of retry attempts on no-answer/busy.
RetryPeriodint?NoMinutes between retry attempts.
CallerIDstring?NoCaller ID shown to the recipient.
Optionsstring?NoAdditional provider-specific options.
SendTimeDateTime?NoSchedule delivery. Combine with Timezone.
Timezonestring?NoWindows Timezone name for SendTime.
SubAccountstring?NoSub-account code for billing separation.
Departmentstring?NoDepartment code.
ChargeCodestring?NoBilling charge code for this send.
MessageIDMessageID?NoSupply your own message ID (otherwise auto-generated).
ReportTostring?NoEmail address to receive delivery reports.
WebhookCallbackURLstring?NoURL for delivery status callbacks.
WebhookCallbackFormatEnums.WebhookCallbackType?NoCallback format.
NotificationTypeEnums.NotificationType?NoNotification delivery mode.
SendModeEnums.SendModeTypeNoSet Test to validate without sending. Default Live.

*The pre-recorded audio itself is normally configured via TemplateID (built in the Dashboard).
†Set directly via Destinations, via the named SendMessage(...) shortcut's toNumber/contactID/groupID/contactIDs/groupIDs/destination parameters, or via VoiceBuilder.AddDestination(...)/AddDestinations(...).
‡Takes base64-encoded WAV audio content directly, not a file path. See the call-flow sample below.

Named-parameter shortcut: client.Messaging.Voice.SendMessage(...) also accepts messageToPeople, destination, toNumber, contactID, groupID, contactIDs, groupIDs, destinations, notificationType, sendMode directly (the model retains MessageToPeople for parity with TTS, but a plain Voice send typically relies on TemplateID instead). For keypads and every other field above, use VoiceBuilder or construct a VoiceModel directly. destination accepts either a plain string or a Destination object directly; anything else throws ArgumentException.

Keypad Fields (KeypadModel)

FieldTypeDescription
ToneintThe DTMF digit this entry responds to.
Playstring?Message played when this key is pressed.
RouteNumberstring?Phone number to route the call to when this key is pressed.
PlaySectionEnums.KeypadPlaySection?Where in the call flow this keypad applies (e.g. Main).
PlayFilestring?Label identifying the audio clip played when this key is pressed, paired with File. Meaningful for Voice only, has no effect on TTS.
Filestring?Base64-encoded WAV audio content played for this key, paired with PlayFile. Meaningful for Voice only, has no effect on TTS.

Destination Fields (Destination)

Destination is shared across every channel; see the Destination Model reference in Getting Started for its full field list and construction patterns. Voice reads MainPhone for its destination; other address fields (ToNumber, MobilePhone, EmailAddress, FaxNumber) are accepted but not read by Voice.

FieldDescription
RecipientGeneric single-value shorthand. Voice treats this as the destination phone number when no more specific field is set. Set by the single-string constructor.
ToNumberAccepted but not read by Voice (used by SMS/WhatsApp/RCS).
MobilePhoneAccepted but not read by Voice (used by SMS/WhatsApp/RCS).
MainPhoneDestination phone number, e.g. "+64211111111". Read by Voice.
EmailAddressAccepted but not read by Voice (used by Email).
FaxNumberAccepted but not read by Voice (used by Fax).
CompanyPersonalisation token [[Company]].
AttentionPersonalisation token [[Attention]].
FirstNamePersonalisation token [[FirstName]].
LastNamePersonalisation token [[LastName]].
Custom1Custom9Arbitrary per-destination personalisation values, [[Custom1]][[Custom9]].
ContactIDAddressbook contact reference. Sends to that contact instead of a raw number.
GroupIDAddressbook group reference. Sends to all members of that group.
GroupCodeAlternative group lookup by code.

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.

var response = client.Messaging.Voice.SendMessage(
    toNumber: "+64211111111",
    sendMode: Enums.SendModeType.Test
);

With a keypad menu, via the builder

Add interactive keypad options so callers can route themselves to a different number by pressing a key.

using var builder = new VoiceBuilder();

var model = builder
    .AddDestination("+64211111111")
    .AddKeypad(new KeypadModel
    {
        Tone = 1,
        RouteNumber = "+64211112222",
        PlaySection = Enums.KeypadPlaySection.Main
    })
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Voice.SendMessage(model);

Call flow with pre-recorded audio and keypad-triggered playback

Set audio for every stage of the call: the main message, the answering machine fallback, the routing announcements, and the end-of-call message, alongside a keypad menu where key 1 routes to another number and key 2 plays its own audio clip. MessageToPeople, MessageToAnswerPhones, the CallRouteMessage* fields, and EndCallMessage all take base64-encoded WAV audio content directly, not a file path.

using var builder = new VoiceBuilder();

var model = builder
    .AddDestination("+64211111111")
    .SetMessageToPeople("[base64-encoded-wav-data]")
    .SetMessageToAnswerPhones("[base64-encoded-wav-data]")
    .SetAnswerPhoneMode(Enums.AnswerPhoneMode.DAS)
    .SetCallRouteMessageToPeople("[base64-encoded-wav-data]")
    .SetCallRouteMessageToOperators("[base64-encoded-wav-data]")
    .SetCallRouteMessageOnWrongKey("[base64-encoded-wav-data]")
    .SetEndCallMessage("[base64-encoded-wav-data]")
    .AddKeypad(new KeypadModel
    {
        Tone = 1,
        RouteNumber = "+64211112222",
        PlaySection = Enums.KeypadPlaySection.Main
    })
    .AddKeypad(new KeypadModel
    {
        Tone = 2,
        PlayFile = "sales-greeting",
        File = "[base64-encoded-wav-data]"
    })
    .SetKeypadOptionRequired(true)
    .SetNumberOfOperators(2)
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Voice.SendMessage(model);

Multiple destinations

Call more than one number in a single request using VoiceBuilder.

using var builder = new VoiceBuilder();

var model = builder
    .AddDestination("+64211111111")
    .AddDestination("+64222222222")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Voice.SendMessage(model);

With personalisation

Build a Destination with MainPhone and personalisation fields set. These are echoed back in the recipient's Status result even though Voice's own pre-recorded audio can't substitute merge tags into speech.

using var builder = new VoiceBuilder();

var model = builder
    .AddDestination(new Destination
    {
        MainPhone = "+64211111111",
        FirstName = "Alice",
        Custom1 = "Account #4432"
    })
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Voice.SendMessage(model);

Scheduled send

Delay the call to a specific SendTime.

using var builder = new VoiceBuilder();

var model = builder
    .AddDestination("+64211111111")
    .SetSendTime(DateTime.Now.AddDays(1))
    .SetTimezone("New Zealand")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Voice.SendMessage(model);

Retry attempts, caller ID, and billing codes

Configure retry behaviour for calls that go unanswered or reach a busy signal, the caller ID shown to the recipient, and the sub-account/department/charge code used for billing and reporting.

using var builder = new VoiceBuilder();

var model = builder
    .AddDestination("+64211111111")
    .SetRetryAttempts(3)
    .SetRetryPeriod(5)
    .SetCallerID("+6499999999")
    .SetSubAccount("SALES")
    .SetDepartment("OUTBOUND")
    .SetChargeCode("CC-001")
    .SetReportTo("reports@example.com")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Voice.SendMessage(model);

Poll for status

Check call progress and per-recipient results any time after sending.

var status = client.Messaging.Voice.Status(response.MessageID);

if (status.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"JobStatus: '{status.JobStatus}', JobNum: '{status.JobNum}'");

    foreach (var recipient in status.Recipients)
    {
        Console.WriteLine($" -> {recipient.Destination}: {recipient.Status} ({recipient.Result})");
    }
}

Actions

Voice supports Reschedule, Abort, Resubmit, and Pacing (see the Getting Started capability table for all channels).

Reschedule

Move a not-yet-placed call to a new sendTime.

var response = client.Actions.Voice.Reschedule(
    messageID: response.MessageID,
    sendTime: DateTime.Parse("2026-12-31T12:00:00")
);

Abort

Cancel a scheduled call before it's placed.

client.Actions.Voice.Abort(response.MessageID);

Resubmit

Resubmit a call for redelivery at a new scheduled time, without rebuilding the whole request.

client.Actions.Voice.Resubmit(response.MessageID, DateTime.Now.AddHours(1));

Pacing (adjust simultaneous operators)

Change how many calls run at once on an in-progress keypad-routed job.

client.Actions.Voice.Pacing(response.MessageID, numberOfOperators: 1);

Response

SendMessage(...)VoiceApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
MessageIDMessageID?The ID of the call you just placed.

Status(...)VoiceStatusApiResult

FieldTypeDescription
MessageIDMessageID?The call this status is for.
JobStatusEnums.JobStatusSee Getting Started's Common Response Enums.
JobNumstring?TNZ's internal job number for this send.
Accountstring?The TNZ account that owns this job.
SubAccount / Departmentstring?Echoed from the original send.
Referencestring?Echoed from the Reference parameter.
CreatedTimeLocal / CreatedTimeUTCDateTime?When the job was created, in local time and UTC.
DelayedTimeLocal / DelayedTimeUTCDateTime?The scheduled send time, if SendTime was set.
Timezonestring?Windows Timezone name used for scheduling.
CountintTotal recipients in the job.
CompleteintRecipients processed so far.
Success / FailedintCalls successfully completed / failed.
Pricedecimal?Job total cost.
TotalRecords / RecordsPerPage / PageCount / PageintPagination metadata for Recipients.
RecipientsList<VoiceRecipientResult>Per-recipient results. See table below.
VoiceRecipientResult (each entry in Recipients)
FieldTypeDescription
TypeEnums.RecipientChannelTypeSee Getting Started's Common Response Enums.
DestSeqstring?TNZ's internal sequence ID for this recipient within the job.
Destinationstring?The recipient's phone number.
ContactIDContactID?Addressbook contact reference, if sent via ContactID/GroupID.
StatusEnums.MessageStatusSee Getting Started's Common Response Enums.
Resultstring?Human-readable call result for this recipient.
SentTimeLocal / SentTimeUTCDateTime?When the call was actually placed to this recipient.
Attention / Company / Custom1Custom9string?Echoed personalisation fields. See Destination Fields above.
RemoteIDstring?Carrier/network-assigned identifier for this call, if available.
Pricedecimal?Per-recipient cost.

Reschedule(...)/Abort(...)/Resubmit(...)/Pacing(...)VoiceActionApiResult

FieldTypeDescription
ActionResultstring?Human-readable result of the action.
MessageIDMessageID?The call this action was applied to.
JobNumstring?The job number this action was applied to.
StatusEnums.JobStatusSee Getting Started's Common Response Enums.
Actionstring?The action performed, e.g. "Pacing".

Fax

Bridge the gap between digital and traditional documentation with TNZ's reliable Fax API. Send documents directly from your application to any fax machine worldwide, with no physical fax hardware required. Track the delivery status of each fax to confirm your important documents arrive successfully.

Unlike SMS/RCS/WhatsApp, Fax has no free-text message field. Content comes entirely from an attachment.

Quick Example

using var builder = new FaxBuilder();

var model = builder
    .AddDestination("+6491111111")
    .AddAttachment("My Document.pdf", "[base64-encoded-file-data]")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Fax.SendMessage(model);

if (response.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Success - MessageID: {response.MessageID}");
}

Parameters (FaxModel)

ParameterTypeRequiredDescription
FilesICollection<Attachment>?Yes*The document(s) to fax.
TemplateIDstring?Yes*Pre-configured fax template ID (alternative to Files).
DestinationsICollection<Destination>?Yes†One or more destinations. See Destination fields below.
ContactIDContactID?NoSingle addressbook contact to send to (alternative/addition to Destinations).
GroupIDGroupID?NoSingle addressbook group to send to (alternative/addition to Destinations).
Referencestring?NoYour internal reference, returned in reports and webhooks.
CSIDstring?NoFax CSID (station identifier).
ResolutionEnums.FaxResolution?NoFax output resolution.
WatermarkFolderstring?NoFolder containing watermark images.
WatermarkFirstPagestring?NoWatermark image applied to the first page only.
WatermarkAllPagesstring?NoWatermark image applied to every page.
RetryAttemptsint?NoNumber of retry attempts on busy/no-answer.
RetryPeriodint?NoMinutes between retry attempts.
SendTimeDateTime?NoSchedule delivery. Combine with Timezone.
Timezonestring?NoWindows Timezone name for SendTime.
SubAccountstring?NoSub-account code for billing separation.
Departmentstring?NoDepartment code.
ChargeCodestring?NoBilling charge code.
MessageIDMessageID?NoSupply your own message ID (otherwise auto-generated).
ReportTostring?NoEmail address to receive delivery reports.
WebhookCallbackURLstring?NoURL for delivery status callbacks.
WebhookCallbackFormatEnums.WebhookCallbackType?NoCallback format.
NotificationTypeEnums.NotificationType?NoNotification delivery mode.
SendModeEnums.SendModeTypeNoSet Test to validate without sending. Default Live.

*Either Files or TemplateID must be provided.
†Set directly via Destinations, via the named SendMessage(...) shortcut's toNumber/contactID/groupID/contactIDs/groupIDs/destination parameters, or via FaxBuilder.AddDestination(...)/AddDestinations(...).

Named-parameter shortcut: client.Messaging.Fax.SendMessage(...) also accepts destination, toNumber, contactID, groupID, contactIDs, groupIDs, destinations, file, attachments, notificationType, sendMode directly. For CSID, Resolution, watermarks, retry settings, etc. use FaxBuilder or construct a FaxModel directly. destination accepts either a plain string or a Destination object directly; anything else throws ArgumentException.

Destination Fields (Destination)

Destination is shared across every channel; see the Destination Model reference in Getting Started for its full field list and construction patterns. Fax reads FaxNumber as the destination; the generic Recipient shorthand (set by AddDestination(string recipient) or the single-string constructor) is also accepted and treated as the fax number when sending via Fax.

FieldDescription
RecipientGeneric single-value shorthand for the fax number. Set by AddDestination(string recipient) or the Destination single-string/named-argument constructor.
ToNumberAccepted but not read by Fax.
MobilePhoneAccepted but not read by Fax.
MainPhoneAccepted but not read by Fax.
EmailAddressAccepted but not read by Fax.
FaxNumberDestination fax number, e.g. "+6491111111". Read by Fax.
CompanyFor reference/reporting. Fax has no message body to personalise.
AttentionFor reference/reporting. Fax has no message body to personalise.
FirstNameFor reference/reporting. Fax has no message body to personalise.
LastNameFor reference/reporting. Fax has no message body to personalise.
Custom1Custom9For reference/reporting. Fax has no message body to personalise.
ContactIDAddressbook contact reference. Sends to that contact instead of a raw number.
GroupIDAddressbook group reference. Sends to all members of that group.
GroupCodeAlternative group lookup by code.

Code Samples

Single destination with an attachment

The simplest way to send: attach a document's base64-encoded content directly.

using var builder = new FaxBuilder();

var model = builder
    .AddDestination("+6491111111")
    .AddAttachment("My Document.pdf", "[base64-encoded-file-data]")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Fax.SendMessage(model);

From a file path

Let AddAttachment read and base64-encode a local file for you, instead of encoding it yourself.

using var builder = new FaxBuilder();

var model = builder
    .AddDestination("+6491111111")
    .AddAttachment(@"C:\Documents\MyDocument.pdf")   // reads and base64-encodes the file
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Fax.SendMessage(model);

Multiple destinations

Send the same document to more than one fax number in a single request.

using var builder = new FaxBuilder();

var model = builder
    .AddDestination("+6491111111")
    .AddDestination("+6492222222")
    .AddAttachment("My Document.pdf", "[base64-encoded-file-data]")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Fax.SendMessage(model);

With reference fields

Build a Destination with FaxNumber and reference fields set. Fax has no message body to personalise, so these are for reference and reporting rather than merge tags.

using var builder = new FaxBuilder();

var model = builder
    .AddDestination(new Destination
    {
        FaxNumber = "+6491111111",
        Attention = "Accounts Payable",
        Custom1 = "Invoice #1234"
    })
    .AddAttachment("My Document.pdf", "[base64-encoded-file-data]")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Fax.SendMessage(model);

Scheduled send

Delay delivery to a specific SendTime.

using var builder = new FaxBuilder();

var model = builder
    .AddDestination("+6491111111")
    .AddAttachment("My Document.pdf", "[base64-encoded-file-data]")
    .SetSendTime(DateTime.Now.AddDays(1))
    .SetTimezone("New Zealand")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Fax.SendMessage(model);

Setting resolution and CSID

Set the output resolution and the CSID (station identifier) printed in the header of the received fax.

using var builder = new FaxBuilder();

var model = builder
    .AddDestination("+6491111111")
    .AddAttachment("My Document.pdf", "[base64-encoded-file-data]")
    .SetResolution(Enums.FaxResolution.High)
    .SetCSID("MY COMPANY")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Fax.SendMessage(model);

Adding a watermark

Apply an image from a watermark folder to the first page only, or to every page, of the outgoing fax.

using var builder = new FaxBuilder();

var model = builder
    .AddDestination("+6491111111")
    .AddAttachment("My Document.pdf", "[base64-encoded-file-data]")
    .SetWatermarkFolder("Confidential")
    .SetWatermarkFirstPage("confidential-stamp.png")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Fax.SendMessage(model);

Configuring retry behaviour

Set how many times to retry a busy or unanswered fax number, and how many minutes to wait between attempts.

using var builder = new FaxBuilder();

var model = builder
    .AddDestination("+6491111111")
    .AddAttachment("My Document.pdf", "[base64-encoded-file-data]")
    .SetRetryAttempts(3)
    .SetRetryPeriod(5)
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.Fax.SendMessage(model);

Poll for status

Check delivery progress and per-recipient results any time after sending.

var status = client.Messaging.Fax.Status(response.MessageID);

if (status.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"JobStatus: '{status.JobStatus}', JobNum: '{status.JobNum}'");

    foreach (var recipient in status.Recipients)
    {
        Console.WriteLine($" -> {recipient.Destination}: {recipient.Status} ({recipient.Result})");
    }
}

Actions

Fax supports Reschedule, Abort, and Resubmit (see the Getting Started capability table for all channels).

Reschedule

Move a not-yet-sent fax to a new sendTime.

var response = client.Actions.Fax.Reschedule(
    messageID: response.MessageID,
    sendTime: DateTime.Parse("2026-12-31T12:00:00")
);

Abort

Cancel a scheduled fax before it sends.

client.Actions.Fax.Abort(response.MessageID);

Resubmit

Resend a fax at a new scheduled time without rebuilding the whole request.

client.Actions.Fax.Resubmit(response.MessageID, DateTime.Now.AddHours(1));

Response

SendMessage(...)FaxApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
MessageIDMessageID?The ID of the fax you just sent.

Status(...)FaxStatusApiResult

FieldTypeDescription
MessageIDMessageID?The fax this status is for.
JobStatusEnums.JobStatusSee Getting Started's Common Response Enums.
JobNumstring?TNZ's internal job number for this send.
Accountstring?The TNZ account that owns this job.
SubAccount / Departmentstring?Echoed from the original send.
Referencestring?Echoed from the Reference parameter.
CreatedTimeLocal / CreatedTimeUTCDateTime?When the job was created, in local time and UTC.
DelayedTimeLocal / DelayedTimeUTCDateTime?The scheduled send time, if SendTime was set.
Timezonestring?Windows Timezone name used for scheduling.
CountintTotal recipients in the job.
CompleteintRecipients processed so far.
Success / FailedintRecipients successfully delivered / failed.
Pricedecimal?Job total cost.
TotalRecords / RecordsPerPage / PageCount / PageintPagination metadata for Recipients.
RecipientsList<FaxRecipientResult>Per-recipient results. See table below.
FaxRecipientResult (each entry in Recipients)
FieldTypeDescription
TypeEnums.RecipientChannelTypeSee Getting Started's Common Response Enums.
DestSeqstring?TNZ's internal sequence ID for this recipient within the job.
Destinationstring?The recipient's fax number.
ContactIDContactID?Addressbook contact reference, if sent via ContactID/GroupID.
StatusEnums.MessageStatusSee Getting Started's Common Response Enums.
Resultstring?Human-readable delivery result for this recipient.
SentTimeLocal / SentTimeUTCDateTime?When the fax was actually sent to this recipient.
Attention / Company / Custom1Custom9string?Echoed personalisation fields. See Destination Fields above.
RemoteIDstring?Carrier/network-assigned identifier for this delivery, if available.
Pricedecimal?Per-recipient cost.

Reschedule(...)/Abort(...)/Resubmit(...)FaxActionApiResult

FieldTypeDescription
ActionResultstring?Human-readable result of the action.
MessageIDMessageID?The fax this action was applied to.
JobNumstring?The job number this action was applied to.
StatusEnums.JobStatusSee Getting Started's Common Response Enums.
Actionstring?The action performed, e.g. "Resubmit".

WhatsApp

WhatsApp is the world's most popular messaging platform. Reach users worldwide with text and files, and receive their responses back (see Poll for status / inbound messages below).

Send WhatsApp template messages, with optional fallback to another channel, via the TNZ REST API. WhatsApp requires both Message and TemplateID (not either/or like SMS). Find your Template ID in the Dashboard.

Quick Example

var response = client.Messaging.WhatsApp.SendMessage(
    messageText: "Your order has shipped!",
    templateId: "123e4567-e89b-12d3-a456-426614174000",
    toNumber: "+64211111111",
    sendMode: Enums.SendModeType.Test
);

if (response.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Success - MessageID: {response.MessageID}");
}

Parameters (WhatsAppModel)

ParameterTypeRequiredDescription
Messagestring?YesMessage body. Supports personalisation tokens [[FirstName]], [[Custom1]], etc.
TemplateIDstring?YesPre-approved WhatsApp template ID, required alongside Message, unlike SMS/RCS's either/or.
DestinationsICollection<Destination>?Yes†One or more destinations. See Destination fields below.
ContactIDContactID?NoSingle addressbook contact to send to (alternative/addition to Destinations).
GroupIDGroupID?NoSingle addressbook group to send to (alternative/addition to Destinations).
Referencestring?NoYour internal reference, returned in reports and webhooks.
FromNumberstring?NoSender ID shown on the recipient's device.
FallbackModeICollection<Enums.WhatsAppFallbackMode>?NoFallback channel(s) if WhatsApp delivery fails, e.g. SMS.
SendTimeDateTime?NoSchedule delivery. Combine with Timezone.
Timezonestring?NoWindows Timezone name for SendTime.
SubAccountstring?NoSub-account code for billing separation.
Departmentstring?NoDepartment code.
MessageIDMessageID?NoSupply your own message ID (otherwise auto-generated).
ReportTostring?NoEmail address to receive delivery reports.
WebhookCallbackURLstring?NoURL for delivery status callbacks.
WebhookCallbackFormatEnums.WebhookCallbackType?NoCallback format.
NotificationTypeEnums.NotificationType?NoNotification delivery mode.
FilesICollection<Attachment>?NoMedia attachments.
SendModeEnums.SendModeTypeNoSet Test to validate without sending. Default Live.

†Set directly via Destinations, via the named SendMessage(...) shortcut's toNumber/contactID/groupID/contactIDs/groupIDs/destination parameters, or via WhatsAppBuilder.AddDestination(...)/AddDestinations(...).

Named-parameter shortcut: client.Messaging.WhatsApp.SendMessage(...) also accepts messageText, templateId, destination, toNumber, contactID, groupID, contactIDs, groupIDs, destinations, file, attachments, notificationType, sendMode directly. For FallbackMode, SendTime, SubAccount, etc. use WhatsAppBuilder or construct a WhatsAppModel directly. destination accepts either a plain string or a Destination object directly; anything else throws ArgumentException.

Destination Fields (Destination)

Destination is shared across every channel; see the Destination Model reference in Getting Started for its full field list and construction patterns. WhatsApp reads ToNumber/MobilePhone as the destination.

FieldDescription
RecipientGeneric single-value shorthand for the destination phone number, e.g. "+64211111111". Read by WhatsApp the same way as ToNumber/MobilePhone.
ToNumberDestination phone number, e.g. "+64211111111". Read by WhatsApp.
MobilePhoneAlternative phone destination field alongside ToNumber. Read by WhatsApp.
MainPhoneAccepted but not read by WhatsApp.
EmailAddressAccepted but not read by WhatsApp.
FaxNumberAccepted but not read by WhatsApp.
CompanyPersonalisation token [[Company]].
AttentionPersonalisation token [[Attention]].
FirstNamePersonalisation token [[FirstName]].
LastNamePersonalisation token [[LastName]].
Custom1Custom9Personalisation tokens [[Custom1]][[Custom9]].
ContactIDAddressbook contact reference. Sends to that contact instead of a raw number.
GroupIDAddressbook group reference. Sends to all members of that group.
GroupCodeAlternative group lookup by code.

Code Samples

Single destination shorthand

The simplest way to send: pass the message, template, and destination directly to SendMessage(...). Remember both messageText and templateId are required.

var response = client.Messaging.WhatsApp.SendMessage(
    messageText: "Your order has shipped!",
    templateId: "123e4567-e89b-12d3-a456-426614174000",
    toNumber: "+64211111111",
    sendMode: Enums.SendModeType.Test
);

With SMS fallback, via the builder

Use WhatsAppBuilder to add a fallback channel for recipients that can't be reached on WhatsApp.

using var builder = new WhatsAppBuilder();

var model = builder
    .SetMessageText("Your order has shipped!")
    .SetTemplateID("123e4567-e89b-12d3-a456-426614174000")
    .AddDestination("+64211111111")
    .AddFallbackMode(Enums.WhatsAppFallbackMode.SMS)
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.WhatsApp.SendMessage(model);

Multiple destinations

Send the same templated message to more than one destination in a single request.

using var builder = new WhatsAppBuilder();

var model = builder
    .SetMessageText("Your order has shipped!")
    .SetTemplateID("123e4567-e89b-12d3-a456-426614174000")
    .AddDestination("+64211111111")
    .AddDestination("+64222222222")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.WhatsApp.SendMessage(model);

With personalisation

Build a Destination with ToNumber and personalisation fields set, for use with [[FirstName]]-style tokens in the message.

using var builder = new WhatsAppBuilder();

var model = builder
    .SetMessageText("Hi [[FirstName]], your order #[[Custom1]] has shipped!")
    .SetTemplateID("123e4567-e89b-12d3-a456-426614174000")
    .AddDestination(new Destination
    {
        ToNumber = "+64211111111",
        FirstName = "Alice",
        Custom1 = "4432"
    })
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.WhatsApp.SendMessage(model);

With an attachment

Attach a file using its base64-encoded content.

using var builder = new WhatsAppBuilder();

var model = builder
    .SetMessageText("Here's your invoice.")
    .SetTemplateID("123e4567-e89b-12d3-a456-426614174000")
    .AddDestination("+64211111111")
    .AddAttachment("Invoice.pdf", "[base64-encoded-file-data]")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.WhatsApp.SendMessage(model);

Poll for status / inbound messages

Check delivery progress, or retrieve inbound replies received in the last timePeriod minutes, any time after sending.

var status = client.Messaging.WhatsApp.Status(response.MessageID);

var received = client.Messaging.WhatsApp.Received(timePeriod: 1440);
if (received.Result == Enums.ResultCode.Success)
{
    foreach (var message in received.Messages)
    {
        Console.WriteLine($" => From: '{message.From}', MessageText: '{message.MessageText}'");
    }
}

Actions

WhatsApp supports Reschedule and Abort (see the Getting Started capability table for all channels).

Reschedule

Move a not-yet-sent message to a new sendTime.

var response = client.Actions.WhatsApp.Reschedule(
    messageID: response.MessageID,
    sendTime: DateTime.Parse("2026-12-31T12:00:00")
);

Abort

Cancel a scheduled message before it sends.

client.Actions.WhatsApp.Abort(response.MessageID);

Response

SendMessage(...)WhatsAppApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
MessageIDMessageID?The ID of the message you just sent.

Status(...)WhatsAppStatusApiResult

FieldTypeDescription
MessageIDMessageID?The message this status is for.
JobStatusEnums.JobStatusSee Getting Started's Common Response Enums.
JobNumstring?TNZ's internal job number for this send.
Accountstring?The TNZ account that owns this job.
SubAccount / Departmentstring?Echoed from the original send.
Referencestring?Echoed from the Reference parameter.
CreatedTimeLocal / CreatedTimeUTCDateTime?When the job was created, in local time and UTC.
DelayedTimeLocal / DelayedTimeUTCDateTime?The scheduled send time, if SendTime was set.
Timezonestring?Windows Timezone name used for scheduling.
CountintTotal recipients in the job.
CompleteintRecipients processed so far.
Success / FailedintRecipients successfully delivered / failed.
Pricedecimal?Job total cost.
TotalRecords / RecordsPerPage / PageCount / PageintPagination metadata for Recipients.
RecipientsList<WhatsAppRecipientResult>Per-recipient results. See table below.
WhatsAppRecipientResult (each entry in Recipients)
FieldTypeDescription
TypeEnums.RecipientChannelTypeSee Getting Started's Common Response Enums.
DestSeqstring?TNZ's internal sequence ID for this recipient within the job.
Destinationstring?The recipient's phone number.
ContactIDContactID?Addressbook contact reference, if sent via ContactID/GroupID.
StatusEnums.MessageStatusSee Getting Started's Common Response Enums.
Resultstring?Human-readable delivery result for this recipient.
SentTimeLocal / SentTimeUTCDateTime?When the message was actually sent to this recipient.
Attention / Company / Custom1Custom9string?Echoed personalisation fields. See Destination Fields above.
RemoteIDstring?Carrier/network-assigned identifier for this delivery, if available.
Pricedecimal?Per-recipient cost.
SMSRepliesList<WhatsAppReplyResult>Inbound replies from this recipient. See table below. The property is named SMSReplies in the SDK even on WhatsApp; it isn't a mistake in this doc.
WhatsAppReplyResult (each entry in SMSReplies)
FieldTypeDescription
ReceivedIDReceivedID?Unique identifier for this reply.
ReceivedTimeLocal / ReceivedTimeUTCDateTime?When the reply was received.
Timezonestring?Windows Timezone name for ReceivedTimeLocal.
Fromstring?The replying phone number.
MessageTextstring?The reply body.

Received(...)WhatsAppReceivedApiResult

FieldTypeDescription
TotalRecords / RecordsPerPage / PageCount / PageintPagination metadata for Messages.
MessagesList<WhatsAppReceivedMessage>Inbound WhatsApp messages. See table below.
WhatsAppReceivedMessage (each entry in Messages)
FieldTypeDescription
ReceivedIDReceivedID?Unique identifier for this message.
MessageIDMessageID?The original outbound message this replies to, if determinable.
JobNumstring?The original send job's number, if applicable.
SubAccount / Departmentstring?Echoed billing codes from the original send.
ReceivedTimeLocal / ReceivedTimeUTC / ReceivedTimeUTC_RFC3339DateTime?When the message was received, in three formats.
Fromstring?The sender's phone number.
ContactIDContactID?Addressbook contact reference, if the sender matched one.
MessageTextstring?The message body.
Timezonestring?Windows Timezone name for ReceivedTimeLocal.
Versionstring?Payload format version.

Reschedule(...)/Abort(...)WhatsAppActionApiResult

FieldTypeDescription
ActionResultstring?Human-readable result of the action.
MessageIDMessageID?The message this action was applied to.
JobNumstring?The job number this action was applied to.
StatusEnums.JobStatusSee Getting Started's Common Response Enums.
Actionstring?The action performed, e.g. "Reschedule".

RCS

Boost your messaging beyond traditional SMS with Rich Communication Services (RCS): send media-rich messages for a more engaging experience. Like SMS, Message/TemplateID are either/or (not both required, unlike WhatsApp).

Quick Example

var response = client.Messaging.RCS.SendMessage(
    messageText: "Test RCS message",
    toNumber: "+64211111111",
    sendMode: Enums.SendModeType.Test
);

if (response.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Success - MessageID: {response.MessageID}");
}

Parameters (RCSModel)

ParameterTypeRequiredDescription
Messagestring?Yes*Message body. Supports personalisation tokens [[FirstName]], [[Custom1]], etc.
TemplateIDstring?Yes*Pre-configured message template ID (alternative to Message).
DestinationsICollection<Destination>?Yes†One or more destinations. See Destination fields below.
ContactIDContactID?NoSingle addressbook contact to send to (alternative/addition to Destinations).
GroupIDGroupID?NoSingle addressbook group to send to (alternative/addition to Destinations).
Referencestring?NoYour internal reference, returned in reports and webhooks.
FromNumberstring?NoSender ID shown on the recipient's device.
FallbackModeICollection<Enums.RCSFallbackMode>?NoFallback channel(s) if RCS delivery fails, e.g. SMS, Voice, or WhatsApp.
SendTimeDateTime?NoSchedule delivery. Combine with Timezone.
Timezonestring?NoWindows Timezone name for SendTime.
SubAccountstring?NoSub-account code for billing separation.
Departmentstring?NoDepartment code.
MessageIDMessageID?NoSupply your own message ID (otherwise auto-generated).
ReportTostring?NoEmail address to receive delivery reports.
WebhookCallbackURLstring?NoURL for delivery status callbacks.
WebhookCallbackFormatEnums.WebhookCallbackType?NoCallback format.
NotificationTypeEnums.NotificationType?NoNotification delivery mode.
SMSEmailReplystring?NoEmail address to receive replies.
CharacterConversionboolNoConvert characters outside the GSM character set automatically. Default false.
FilesICollection<Attachment>?NoMedia attachments.
SendModeEnums.SendModeTypeNoSet Test to validate without sending. Default Live.

*Either Message or TemplateID must be provided.
†Set directly via Destinations, via the named SendMessage(...) shortcut's toNumber/contactID/groupID/contactIDs/groupIDs/destination parameters, or via RCSBuilder.AddDestination(...)/AddDestinations(...).

Named-parameter shortcut: client.Messaging.RCS.SendMessage(...) also accepts messageText, destination, toNumber, contactID, groupID, contactIDs, groupIDs, destinations, file, attachments, notificationType, sendMode directly. For SendTime, SubAccount, SMSEmailReply, etc. use RCSBuilder or construct an RCSModel directly. destination accepts either a plain string or a Destination object directly; anything else throws ArgumentException.

Destination Fields (Destination)

Destination is shared across every channel; see the Destination Model reference in Getting Started for its full field list and construction patterns. RCS reads ToNumber/MobilePhone as the destination.

FieldDescription
RecipientGeneric single-value shorthand. RCS treats this as the destination phone number when no more specific field is set. Set by the single-string constructor.
ToNumberDestination phone number, e.g. "+64211111111". Read by RCS.
MobilePhoneAlternative phone destination field, also read by RCS.
MainPhoneAccepted but not read by RCS (used by TTS/Voice).
EmailAddressAccepted but not read by RCS (used by Email).
FaxNumberAccepted but not read by RCS (used by Fax).
CompanyPersonalisation token [[Company]].
AttentionPersonalisation token [[Attention]].
FirstNamePersonalisation token [[FirstName]].
LastNamePersonalisation token [[LastName]].
Custom1Custom9Arbitrary per-destination personalisation values, [[Custom1]][[Custom9]].
ContactIDAddressbook contact reference. Sends to that contact instead of a raw number.
GroupIDAddressbook group reference. Sends to all members of that group.
GroupCodeAlternative group lookup by code.

Code Samples

Single destination shorthand

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

var response = client.Messaging.RCS.SendMessage(
    messageText: "Test RCS message",
    toNumber: "+64211111111",
    sendMode: Enums.SendModeType.Test
);

Via the builder, with a custom sender ID

Use RCSBuilder to set fields the named-parameter shortcut doesn't cover, such as a custom FromNumber sender ID.

using var builder = new RCSBuilder();

var model = builder
    .SetMessageText("Test RCS message")
    .SetFromNumber("61410023004")   // Sender ID, E.164 without leading '+'
    .AddDestination("+64211111111")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.RCS.SendMessage(model);

With SMS fallback

Add one or more fallback channels with AddFallbackMode(...), tried in order if RCS delivery fails.

using var builder = new RCSBuilder();

var model = builder
    .SetMessageText("Test RCS message")
    .AddDestination("+64211111111")
    .AddFallbackMode(Enums.RCSFallbackMode.SMS)
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.RCS.SendMessage(model);

Multiple destinations

Send the same message to more than one destination in a single request.

using var builder = new RCSBuilder();

var model = builder
    .SetMessageText("Test RCS message")
    .AddDestination("+64211111111")
    .AddDestination("+64222222222")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.RCS.SendMessage(model);

With personalisation

Build a Destination with ToNumber and personalisation fields set, for use with [[FirstName]]-style tokens in the message.

using var builder = new RCSBuilder();

var model = builder
    .SetMessageText("Hi [[FirstName]], your order #[[Custom1]] has shipped!")
    .AddDestination(new Destination
    {
        ToNumber = "+64211111111",
        FirstName = "Alice",
        Custom1 = "4432"
    })
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.RCS.SendMessage(model);

Scheduled send

Delay delivery to a specific SendTime.

using var builder = new RCSBuilder();

var model = builder
    .SetMessageText("Your reminder.")
    .AddDestination("+64211111111")
    .SetSendTime(DateTime.Now.AddDays(1))
    .SetTimezone("New Zealand")
    .SetSendMode(Enums.SendModeType.Test)
    .Build();

var response = client.Messaging.RCS.SendMessage(model);

Poll for status / inbound messages

Check delivery progress, or retrieve inbound replies received in the last timePeriod minutes, any time after sending.

var status = client.Messaging.RCS.Status(response.MessageID);

var received = client.Messaging.RCS.Received(timePeriod: 1440);
if (received.Result == Enums.ResultCode.Success)
{
    foreach (var message in received.Messages)
    {
        Console.WriteLine($" => From: '{message.From}', MessageText: '{message.MessageText}'");
    }
}

Actions

RCS supports Reschedule and Abort (see the Getting Started capability table for all channels).

Reschedule

Move a not-yet-sent message to a new sendTime.

var response = client.Actions.RCS.Reschedule(
    messageID: response.MessageID,
    sendTime: DateTime.Parse("2026-12-31T12:00:00")
);

Abort

Cancel a scheduled message before it sends.

client.Actions.RCS.Abort(response.MessageID);

Response

SendMessage(...)RCSApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
MessageIDMessageID?The ID of the message you just sent.

Status(...)RCSStatusApiResult

FieldTypeDescription
MessageIDMessageID?The message this status is for.
JobStatusEnums.JobStatusSee Getting Started's Common Response Enums.
JobNumstring?TNZ's internal job number for this send.
Accountstring?The TNZ account that owns this job.
SubAccount / Departmentstring?Echoed from the original send.
Referencestring?Echoed from the Reference parameter.
CreatedTimeLocal / CreatedTimeUTCDateTime?When the job was created, in local time and UTC.
DelayedTimeLocal / DelayedTimeUTCDateTime?The scheduled send time, if SendTime was set.
Timezonestring?Windows Timezone name used for scheduling.
CountintTotal recipients in the job.
CompleteintRecipients processed so far.
Success / FailedintRecipients successfully delivered / failed.
Pricedecimal?Job total cost.
TotalRecords / RecordsPerPage / PageCount / PageintPagination metadata for Recipients.
RecipientsList<RCSRecipientResult>Per-recipient results. See table below.
RCSRecipientResult (each entry in Recipients)
FieldTypeDescription
TypeEnums.RecipientChannelTypeSee Getting Started's Common Response Enums.
DestSeqstring?TNZ's internal sequence ID for this recipient within the job.
Destinationstring?The recipient's phone number.
ContactIDContactID?Addressbook contact reference, if sent via ContactID/GroupID.
StatusEnums.MessageStatusSee Getting Started's Common Response Enums.
Resultstring?Human-readable delivery result for this recipient.
SentTimeLocal / SentTimeUTCDateTime?When the message was actually sent to this recipient.
Attention / Company / Custom1Custom9string?Echoed personalisation fields. See Destination Fields above.
RemoteIDstring?Carrier/network-assigned identifier for this delivery, if available.
Pricedecimal?Per-recipient cost.
SMSRepliesList<RCSReplyResult>Inbound replies from this recipient. See table below. The property is named SMSReplies in the SDK even on RCS; it isn't a mistake in this doc.
RCSReplyResult (each entry in SMSReplies)
FieldTypeDescription
ReceivedIDReceivedID?Unique identifier for this reply.
ReceivedTimeLocal / ReceivedTimeUTCDateTime?When the reply was received.
Timezonestring?Windows Timezone name for ReceivedTimeLocal.
Fromstring?The replying phone number.
MessageTextstring?The reply body.

Received(...)RCSReceivedApiResult

FieldTypeDescription
TotalRecords / RecordsPerPage / PageCount / PageintPagination metadata for Messages.
MessagesList<RCSReceivedMessage>Inbound RCS messages. See table below.
RCSReceivedMessage (each entry in Messages)
FieldTypeDescription
ReceivedIDReceivedID?Unique identifier for this message.
MessageIDMessageID?The original outbound message this replies to, if determinable.
JobNumstring?The original send job's number, if applicable.
SubAccount / Departmentstring?Echoed billing codes from the original send.
ReceivedTimeLocal / ReceivedTimeUTC / ReceivedTimeUTC_RFC3339DateTime?When the message was received, in three formats.
Fromstring?The sender's phone number.
ContactIDContactID?Addressbook contact reference, if the sender matched one.
MessageTextstring?The message body.
Timezonestring?Windows Timezone name for ReceivedTimeLocal.
Versionstring?Payload format version.

Reschedule(...)/Abort(...)RCSActionApiResult

FieldTypeDescription
ActionResultstring?Human-readable result of the action.
MessageIDMessageID?The message this action was applied to.
JobNumstring?The job number this action was applied to.
StatusEnums.JobStatusSee Getting Started's Common Response Enums.
Actionstring?The action performed, e.g. "Reschedule".

Inbound Webhooks

Get real-time updates without constant polling. TNZ's webhooks notify your application instantly when a message completes sending, or when you receive an inbound SMS. This event-driven approach keeps your system up to date with delivery receipts and customer replies, saving resources compared to polling Status(...)/Received(...) on a timer.

Webhooks are inbound: TNZ's servers POST these payloads to your own server (configure WebhookCallbackURL on a Send call, or your Sender's Dashboard settings for SMS-reply/result reporting). The SDK doesn't call anything for this; instead, TNZAPI.NET.Webhooks provides typed payload models you can deserialize an incoming request body into from your own webhook receiver endpoint.

Payload Fields

ResultWebhookPayload and InboundSMSWebhookPayload share nearly identical shapes, with two exceptions worth knowing about: ReceivedID is string? on ResultWebhookPayload but the strongly-typed ReceivedID? on InboundSMSWebhookPayload; and Status is the strongly-typed Enums.MessageStatus? on ResultWebhookPayload but plain string? on InboundSMSWebhookPayload.

FieldTypeDescription
Versionstring?Webhook payload format version.
Senderstring?Your TNZ Sender ID.
APIKeystring?Your API key, included for correlation/validation.
Typestring?Message channel type (e.g. "SMS").
Destinationstring?The recipient (or, for inbound SMS, the sender) address/number.
ContactIDContactID?Addressbook contact reference, if the destination matched one.
ReceivedIDSee note aboveIdentifier for this specific inbound/result event.
MessageIDMessageID?The original outbound message this event relates to.
SubAccountstring?Sub-account code, echoed from the original send.
Departmentstring?Department code, echoed from the original send.
JobNumberstring?Job number for the send batch.
SentTimeLocal / SendTimeUTC / SentTimeUTC_RFC3339DateTime?Event timestamp in local time, UTC, and RFC3339 UTC respectively.
StatusSee note aboveDelivery/message status.
Resultstring?Result code/description for this event.
Messagestring?The message text (the reply body for inbound SMS).
Pricestring?See the Price note below.
Detailstring?Additional detail text for this event.
URLstring?Related URL, if applicable.

Code Samples

Delivery result webhook

Handle the payload TNZ posts when a message completes sending, whatever the outcome, as an alternative to polling Status(...).

// Inside your own ASP.NET (or similar) webhook receiver action:
var payload = System.Text.Json.JsonSerializer.Deserialize<TNZAPI.NET.Webhooks.ResultWebhookPayload>(requestBody);

Console.WriteLine($"{payload.Type} to {payload.Destination}: {payload.Status} ({payload.Result})");

Inbound SMS webhook

Handle the payload TNZ posts when a recipient replies to an SMS, as an alternative to polling Received(...).

var inboundSms = System.Text.Json.JsonSerializer.Deserialize<TNZAPI.NET.Webhooks.InboundSMSWebhookPayload>(requestBody);

Console.WriteLine($"Inbound SMS from {inboundSms.Destination}: {inboundSms.Message}");

Both payload types deserialize correctly with your own default JsonSerializerOptions. No need to configure anything on your end, including TNZ's non-standard timestamp format.

Note: Price on both payload types is string?, not decimal?. TNZ's webhook callback may send it as either a JSON string or a JSON number, and the SDK tolerantly parses either into a string so you get a consistent type either way. This differs from the decimal? Price exposed by each channel's Status(...) result (job total and per-recipient). Parse it yourself, e.g. decimal.Parse(payload.Price, CultureInfo.InvariantCulture), if you need a numeric value from a webhook payload.

Addressbook

Centralise your contacts with the Addressbook: a single source of truth that simplifies your integration and enables data-rich personalisation across every messaging channel. Manage contacts, groups, and contact–group relationships; keep contact data synchronised with your CRM, HR system, or spreadsheets; organise contacts into groups to message thousands with a single GroupID; and reduce payload size by referencing a ContactID/GroupID instead of sending full recipient details on every send. Personalisation fields (FirstName, Company, Custom1Custom4) 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.

Contact

Fields (ContactModel)

FieldTypeDescription
ExTypestringExternal system type tag, for correlating this contact with your own CRM/system.
ExIDstringExternal system ID, for correlating this contact with your own CRM/system.
ViewByEnums.ViewEditByOptions?Who can view this contact in the Dashboard: Account, SubAccount, Department, or No.
EditByEnums.ViewEditByOptions?Who can edit this contact in the Dashboard, same values as ViewBy.
AccessControlEnums.AccessControlLevel?Limited or Granted.
AttentionstringPersonalisation token [[Attention]].
Titlestringe.g. "Mr", "Dr".
CompanystringPersonalisation token [[Company]].
RecipDepartmentstringThe contact's department at their company. Not related to your TNZ Department code.
FirstNamestringPersonalisation token [[FirstName]].
LastNamestringPersonalisation token [[LastName]].
PositionstringJob title.
StreetAddress / Suburb / City / State / Country / PostcodestringPostal address fields.
MainPhonestringPrimary phone number.
DirectPhonestringDirect-dial phone number.
AltPhone1AltPhone8stringUp to 8 additional phone numbers.
MobilePhonestringMobile number, used as the SMS/WhatsApp/RCS destination when sending via ContactID.
FaxNumberstringFax destination.
EmailAddressstringEmail destination.
WebAddressstringWebsite URL.
Custom1Custom4stringPersonalisation tokens [[Custom1]][[Custom4]].
NotesstringFree-text notes. Not exposed as a personalisation token.

Create

Build a contact with ContactBuilder and save it to your Addressbook.

using var builder = new ContactBuilder();

var contact = builder
    .SetAttention("API Test")
    .SetFirstName("API")
    .SetLastName("Test")
    .SetMobilePhone("+64211231234")
    .SetEmailAddress("test@example.com")
    .SetMainPhone("+6491112222")
    .Build();

var response = client.Addressbook.Contact.Create(contact);

if (response.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Created ContactID={response.ContactID}");
}

Details

Look up a contact's stored fields by ContactID.

var details = client.Addressbook.Contact.Details(response.ContactID);

if (details.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"{details.FirstName} {details.LastName}, {details.EmailAddress}");
}

Update and Delete

Change or remove a contact by ContactID.

var updated = client.Addressbook.Contact.Update(response.ContactID, new ContactModel
{
    Company = "Example Company"
});

client.Addressbook.Contact.Delete(response.ContactID);

Search and List

Find contacts by any combination of emailAddress (full match) or mobilePhone/mainPhone/attention/firstName/lastName/company (partial match), or page through your full contact list.

var results = client.Addressbook.Contact.Search(
    firstName: "Alice",
    company: "Example Company",
    page: 1,
    recordsPerPage: 100
);

if (results.Result == Enums.ResultCode.Success)
{
    foreach (var contact in results.Contacts)
    {
        Console.WriteLine($"{contact.ContactID}: {contact.FirstName} {contact.LastName}");
    }
}

var page = client.Addressbook.Contact.List(page: 1, recordsPerPage: 100);

if (page.Result == Enums.ResultCode.Success)
{
    foreach (var contact in page.Contacts)
    {
        Console.WriteLine($"{contact.ContactID}: {contact.FirstName} {contact.LastName}");
    }
}

Group

Fields (GroupModel)

FieldTypeDescription
GroupNamestringDisplay name for the group.
SubAccountstringSub-account code.
DepartmentstringDepartment code.
ViewEditByEnums.ViewEditByOptions?Who can view and edit this group in the Dashboard: Account, SubAccount, Department, or No. Unlike Contact, Group has one combined permission rather than separate ViewBy/EditBy fields.
AccessControlEnums.AccessControlLevel?Limited or Granted.

Create

Build a group with GroupBuilder and save it to your Addressbook. GroupCode is server-assigned; it's returned on the response, not something you set.

using var builder = new GroupBuilder();

var group = builder
    .SetGroupName("API Test Group")
    .SetSubAccount("SALES")
    .SetViewEditBy(Enums.ViewEditByOptions.SubAccount)
    .Build();

var response = client.Addressbook.Group.Create(group);

if (response.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Created GroupID={response.GroupID}, GroupCode={response.GroupCode}");
}

Details, Update, Delete, and List

Manage a group the same way as a contact: look up, rename, remove, or page through all groups.

var details = client.Addressbook.Group.Details(response.GroupID);
client.Addressbook.Group.Update(response.GroupID, new GroupModel { GroupName = "Renamed Group" });
client.Addressbook.Group.Delete(response.GroupID);

var page = client.Addressbook.Group.List(page: 1, recordsPerPage: 100);

Contact ↔ Group Relationships

Both directions support the full set of operations: Contact.Group manages one contact's group memberships (list, add, remove, and detail), and Group.Contact manages one group's contact memberships the same way (list, add, remove, and detail).

// Groups a contact belongs to
var groups = client.Addressbook.Contact.Group.List(contactID);

// Add a contact to a group
var addResult = client.Addressbook.Contact.Group.Add(contactID, groupID);

if (addResult.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Added to group: {addResult.Group.GroupName}");
}

// Look up a single contact-group relation
var relation = client.Addressbook.Contact.Group.Detail(contactID, groupID);

// Remove a contact from a group
client.Addressbook.Contact.Group.Remove(contactID, groupID);

// Contacts belonging to a group
var contacts = client.Addressbook.Group.Contact.List(groupID);

if (contacts.Result == Enums.ResultCode.Success)
{
    foreach (var contact in contacts.Contacts)
    {
        Console.WriteLine($"{contact.FirstName} {contact.LastName}");
    }
}

// Add a contact to a group from the Group side
var groupAddResult = client.Addressbook.Group.Contact.Add(groupID, contactID);

if (groupAddResult.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Added {groupAddResult.Contact.FirstName} {groupAddResult.Contact.LastName} to group");
}

// Remove a contact from a group from the Group side
client.Addressbook.Group.Contact.Remove(groupID, contactID);

// Look up a single group-contact relation
var groupRelation = client.Addressbook.Group.Contact.Detail(groupID, contactID);

Response

Every Addressbook result implements IApiResult: check Result before reading other fields; see Getting Started. List-style results additionally carry TotalRecords/RecordsPerPage/PageCount/Page (all int) for pagination.

Contact.Create(...)/Details(...)/Update(...)/Delete(...)ContactApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
ContactIDContactID?The contact's ID.
Ownerstring?The TNZ user who owns this contact.
CreatedTimeLocal / CreatedTimeUTC / CreatedTimeUTC_RFC3339DateTime?When the contact was created, in local time, UTC, and RFC3339 UTC respectively.
UpdatedTimeLocal / UpdatedTimeUTC / UpdatedTimeUTC_RFC3339DateTime?When the contact was last updated.
Timezonestring?Timezone the local timestamps above are expressed in.
every ContactModel field abovenullable equivalentEchoed back, e.g. FirstName, EmailAddress, Custom1Custom4. See the Fields table above.

Contact.Search(...)/List(...)ContactListApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
ContactsList<ContactDetail>The matching contacts for this page, each shaped like Contact.Details(...)'s result.

Group.Create(...)/Details(...)/Update(...)/Delete(...)GroupApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
GroupIDGroupID?The group's ID.
GroupCodestring?Server-assigned lookup code. Read-only; there's no matching field on GroupModel to set it.
GroupName / SubAccount / Department / ViewEditBy / AccessControlnullable equivalentEchoed back. See the Fields table above.
Ownerstring?The TNZ user who owns this group.
CreatedTimeLocal / CreatedTimeUTC / CreatedTimeUTC_RFC3339DateTime?When the group was created. Unlike Contact, Group has no Updated* timestamps.
Timezonestring?Timezone the local timestamp above is expressed in.

Group.List(...)GroupListApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
GroupsList<GroupDetail>The groups for this page, each shaped like Group.Details(...)'s result.

Contact.Group.List(...)ContactGroupListApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
ContactContactDetail?The contact whose groups you're listing.
GroupsList<GroupDetail>The groups this contact belongs to.

Group.Contact.List(...)GroupContactListApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
GroupGroupDetail?The group whose members you're listing.
ContactsList<ContactDetail>The contacts belonging to this group.

Contact.Group.Add(...)/Remove(...)/Detail(...) and Group.Contact.Add(...)/Remove(...)/Detail(...)ContactGroupRelationApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
GroupGroupDetail?The group side of this relation.
ContactContactDetail?The contact side of this relation.

This result type carries no pagination fields, unlike the two List results above; it always describes exactly one contact–group relation.

OptOut

Programmatically manage contacts who have unsubscribed, helping you meet your obligations under anti-spam regulations. Opt-outs are managed on a SubAccount, Department, and message-type (DestType) basis: a contact can opt out of SMS marketing while still receiving important Email alerts, since each channel is suppressed independently.

Once a destination is opted out, sending to it doesn't fail outright: the API accepts the request, but delivery is blocked and the response reports a "Destination is blacklisted" result. This gives you a clear audit trail rather than a hard error. Use List(...) below to retrieve the full opt-out list for auditing, reporting, or syncing with your own CRM.

Enums.OptOutDestType covers the channels OptOut actually supports: SMS, Email, Voice, Fax. WhatsApp and RCS have no blacklist/suppression support in the backend; submitting either as a DestType throws a server-side error. OptOutBuilder.SetDestType(...) and List(...) both accept the enum directly, or a plain string if you need a value the enum doesn't cover.

Fields (OptOutModel)

FieldTypeDescription
DestTypestringRequired. The channel this entry applies to. See Enums.OptOutDestType above.
DestinationstringThe destination to suppress, e.g. "+6421003004" or an email address. Use Destination or ContactID, not both.
ContactIDContactID?Opt out an addressbook contact instead of a raw destination. Use Destination or ContactID, not both.
SubAccountstringScope this entry to a sub-account. Empty applies to all sub-accounts.
DepartmentstringScope this entry to a department. Empty applies to all departments.
StopMessagestringThe opt-out phrase detected, e.g. "Stop sending me these messages".
NotesstringFree-text notes.

Code Samples

Create a single OptOut entry

Suppress future sends to one destination on a specific channel, using Enums.OptOutDestType for the channel.

using var builder = new OptOutBuilder();

var entry = builder
    .SetDestType(Enums.OptOutDestType.SMS)
    .SetDestination("+6421003004")
    .SetNotes("Requested via support call")
    .Build();

var response = client.Configuration.OptOut.Create(entry);

if (response.Result == Enums.ResultCode.Success)
{
    Console.WriteLine($"Created OptOut ID={response.ID}");
}

Opt out an addressbook contact

Suppress an addressbook contact by ContactID instead of a raw destination. Set ContactID or Destination, not both.

using var builder = new OptOutBuilder();

var entry = builder
    .SetDestType(Enums.OptOutDestType.Email)
    .SetContactID(contactID)
    .Build();

var response = client.Configuration.OptOut.Create(entry);

Opt out multiple destinations at once

Suppress a batch of destinations on the same channel in a single call. OptOutBatchModel has no builder, so DestType stays a plain string here; use Enums.OptOutDestType's .ToString() for compile-time safety.

var batchResponse = client.Configuration.OptOut.CreateBatch(new OptOutBatchModel
{
    DestType = Enums.OptOutDestType.SMS.ToString(),
    Destinations = new List<string> { "+6421003004", "+6421003005" }
});

if (batchResponse.Result == Enums.ResultCode.Success)
{
    foreach (var entry in batchResponse.OptOuts)
    {
        Console.WriteLine($"Opted out: {entry.Destination} ({entry.ID})");
    }
}

Update

Change the notes or scoping on an existing entry by OptOutID. DestType and Destination/ContactID can be changed the same way, though moving an entry to a different destination is unusual; deleting and re-creating is more common for that case.

var updated = client.Configuration.OptOut.Update(response.ID, new OptOutModel
{
    Notes = "Confirmed via follow-up call"
});

Details, Delete, and List

Look up a single entry, remove an opt-out, or page through the full suppression list, filtering by channel with Enums.OptOutDestType.

var details = client.Configuration.OptOut.Details(response.ID);
client.Configuration.OptOut.Delete(response.ID);

var list = client.Configuration.OptOut.List(destType: Enums.OptOutDestType.SMS, timePeriod: 30);

if (list.Result == Enums.ResultCode.Success)
{
    foreach (var entry in list.OptOuts)
    {
        Console.WriteLine($"{entry.Destination}, {entry.DestType}");
    }
}

Response

Every OptOut result implements IApiResult: check Result before reading other fields; see Getting Started.

Create(...)/Details(...)/Update(...)/Delete(...)OptOutApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
IDOptOutID?This entry's ID.
DestType / Destination / ContactID / SubAccount / Department / StopMessage / Notesnullable equivalentEchoed back. See the Fields table above.
OriginalMessagestring?The original inbound message that triggered the opt-out, when it was created automatically from a [[STOP]]-style reply rather than via this API.
CreatedTimeLocal / CreatedTimeUTC / CreatedTimeUTC_RFC3339DateTime?When the entry was created, in local time, UTC, and RFC3339 UTC respectively.
UpdatedTimeLocal / UpdatedTimeUTC / UpdatedTimeUTC_RFC3339DateTime?When the entry was last updated.
Timezonestring?Timezone the local timestamps above are expressed in.

CreateBatch(...)OptOutBatchApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
OptOutsList<OptOutDetail>One entry per destination in the batch, each shaped like Details(...)'s result.

List(...)OptOutListApiResult

FieldTypeDescription
ResultEnums.ResultCodeSee Getting Started.
ErrorMessageList<string>See Getting Started.
TotalRecords / RecordsPerPage / PageCount / PageintPagination.
OptOutsList<OptOutDetail>The matching entries for this page.