Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ export * from './sns.handler';
export * from './telnyx.handler';
export * from './termii.handler';
export * from './twilio.handler';
export * from './tnz-sms.handler';
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ChannelTypeEnum, ICredentials, SmsProviderIdEnum } from '@novu/shared';
import { TnzSmsProvider } from '@novu/providers';
import { BaseSmsHandler } from './base.handler';

export class TnzSmsHandler extends BaseSmsHandler {
constructor() {
super(SmsProviderIdEnum.Tnz, ChannelTypeEnum.SMS);
}

buildProvider(credentials: ICredentials) {
if (!credentials.apiKey) {
throw new Error('Invalid credentials');
}

const config = {
authToken: credentials.apiKey,
};

this.provider = new TnzSmsProvider(config);
}
}
2 changes: 2 additions & 0 deletions libs/application-generic/src/factories/sms/sms.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
SnsHandler,
TelnyxHandler,
TermiiSmsHandler,
TnzSmsHandler,
TwilioHandler,
} from './handlers';
import { ISmsFactory, ISmsHandler } from './interfaces';
Expand Down Expand Up @@ -67,6 +68,7 @@ export class SmsFactory implements ISmsFactory {
new EazySmsHandler(),
new MobishastraHandler(),
new AfroSmsHandler(),
new TnzSmsHandler(),
];

getHandler(integration: IntegrationEntity) {
Expand Down
1 change: 1 addition & 0 deletions packages/framework/src/schemas/providers/sms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ export const smsProviderSchemas = {
termii: genericProviderSchemas,
twilio: twilioProviderSchemas,
'afro-message': genericProviderSchemas,
'tnz-sms': genericProviderSchemas,
} as const satisfies Record<SmsProviderIdEnum, { output: JsonSchema }>;
1 change: 1 addition & 0 deletions packages/framework/src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export enum SmsProviderIdEnum {
EazySms = 'eazy-sms',
Mobishastra = 'mobishastra',
AfroSms = 'afro-message',
Tnz = 'tnz-sms',
}

export enum ChatProviderIdEnum {
Expand Down
1 change: 1 addition & 0 deletions packages/providers/src/lib/sms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ export * from './telnyx/telnyx.interface';
export * from './telnyx/telnyx.provider';
export * from './termii/termii.provider';
export * from './twilio/twilio.provider';
export * from './tnz-sms/tnz-sms.provider';
95 changes: 95 additions & 0 deletions packages/providers/src/lib/sms/tnz-sms/tnz-sms.provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { ChannelTypeEnum, ISendMessageSuccessResponse, ISmsOptions, ISmsProvider } from '@novu/stateless';
import axios, { AxiosInstance } from 'axios';
import { SmsProviderIdEnum } from '@novu/shared';
import { BaseProvider, CasingEnum } from '../../../base.provider';
import { WithPassthrough } from '../../../utils/types';

export class TnzSmsProvider extends BaseProvider implements ISmsProvider {
id = SmsProviderIdEnum.Tnz;
channelType = ChannelTypeEnum.SMS as ChannelTypeEnum.SMS;
protected casing: CasingEnum = CasingEnum.PASCAL_CASE;
private readonly BASE_URL = 'https://api.tnz.co.nz/api/v2.04/send/';
private axiosInstance: AxiosInstance;

constructor(
private config: {
authToken: string;
}
) {
super();

// Handle auth token format - accept either just the token or full "Basic TOKEN" format
const authToken = this.config.authToken.startsWith('Basic ')
? this.config.authToken
: `Basic ${this.config.authToken}`;

this.axiosInstance = axios.create({
baseURL: this.BASE_URL,
headers: {
'Content-Type': "application/json; encoding='utf-8'",
Accept: "application/json; encoding='utf-8'",
Authorization: authToken,
},
});
}

async sendMessage(
options: ISmsOptions,
bridgeProviderData: WithPassthrough<Record<string, unknown>> = {}
): Promise<ISendMessageSuccessResponse> {
const { to, content, customData } = options;

const destination = { Recipient: to };
const messageData = {
Message: content,
Destinations: [destination],
};

// Add optional TNZ fields if provided in customData
if (customData) {
const optionalFields = [
'MessageID',
'Reference',
'WebhookCallbackURL',
'WebhookCallbackFormat',
'SendTime',
'TimeZone',
'SubAccount',
'Department',
'ChargeCode',
'FromNumber',
'SMSEmailReply',
'CharacterConversion',
'Files',
];

for (const field of optionalFields) {
if (customData[field] !== undefined) {
messageData[field] = customData[field];
}
}
}

const payload = { MessageData: messageData };

const data = this.transform(bridgeProviderData, payload);

try {
const response = await this.axiosInstance.post('sms', data.body);

if (response.data.Result === 'Success' || response.data.MessageID) {
return {
id: response.data.MessageID,
date: new Date().toISOString(),
};
} else {
throw new Error('Unexpected response format from TNZ API');
}
} catch (error) {
if (error.response?.data?.ErrorMessage) {
throw new Error(`TNZ API Error: ${error.response.data.ErrorMessage}`);
}
throw error;
}
}
}
Loading
Loading