Skip to content
Merged
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
8 changes: 4 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node: ["14", "15", "16", "17", "18"]
node: ["14", "15", "16", "17", "18" ]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: setup node
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- uses: actions/cache@v2
- uses: actions/cache@v4
with:
path: node_modules
key: customerio-${{ matrix.node }}-${{ hashFiles('package-lock.json') }}
Expand Down
12 changes: 10 additions & 2 deletions lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { RequestOptions } from 'https';
import Request, { BearerAuth, RequestData } from './request';
import { Region, RegionUS } from './regions';
import { SendEmailRequest, SendPushRequest } from './api/requests';
import { SendEmailRequest, SendPushRequest, SendSMSRequest } from './api/requests';
import { cleanEmail, isEmpty, isIdentifierType, MissingParamError } from './utils';
import { Filter, IdentifierType } from './types';

Expand Down Expand Up @@ -85,6 +85,14 @@ export class APIClient {
return this.request.post(`${this.apiRoot}/send/push`, req.message);
}

sendSMS(req: SendSMSRequest) {
if (!(req instanceof SendSMSRequest)) {
throw new Error('"request" must be an instance of SendSMSRequest');
}

return this.request.post(`${this.apiRoot}/send/sms`, req.message);
}

getCustomersByEmail(email: string) {
if (typeof email !== 'string' || isEmpty(email)) {
throw new Error('"email" must be a string');
Expand Down Expand Up @@ -160,4 +168,4 @@ export class APIClient {
}
}

export { SendEmailRequest, SendPushRequest } from './api/requests';
export { SendEmailRequest, SendPushRequest, SendSMSRequest } from './api/requests';
37 changes: 37 additions & 0 deletions lib/api/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,40 @@ export class SendPushRequest {
}
}
}

export type SMSMessage = Partial<SendSMSRequestOptions>;

export type SendSMSRequestRequiredOptions = {
identifiers: Identifiers;
transactional_message_id: string | number;
};

export type SendSMSRequestOptionalOptions = Partial<{
to: string;
disable_message_retention: boolean;
send_to_unsubscribed: boolean;
queue_draft: boolean;
message_data: Record<string, any>;
send_at: number;
language: string;
}>;

export type SendSMSRequestOptions = SendSMSRequestRequiredOptions & SendSMSRequestOptionalOptions & {};

export class SendSMSRequest {
message: SMSMessage;

constructor(opts: SendSMSRequestOptions) {
this.message = {
identifiers: opts.identifiers,
to: opts.to,
transactional_message_id: opts.transactional_message_id,
disable_message_retention: opts.disable_message_retention,
send_to_unsubscribed: opts.send_to_unsubscribed,
queue_draft: opts.queue_draft,
message_data: opts.message_data,
send_at: opts.send_at,
language: opts.language,
};
}
}
66 changes: 66 additions & 0 deletions test/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
DeliveryExportRequestOptions,
SendEmailRequest,
SendPushRequest,
SendSMSRequest,
} from '../lib/api';
import { RegionUS, RegionEU } from '../lib/regions';
import { Filter, IdentifierType } from '../lib/types';
Expand Down Expand Up @@ -499,3 +500,68 @@ test('#getAttributes: success with type email', (t) => {
),
);
});

test('sendSMS: passing in a plain object throws an error', (t) => {
sinon.stub(t.context.client.request, 'post');

let req = { identifiers: { id: '2' }, transactional_message_id: 1 };

t.throws(() => t.context.client.sendSMS(req as any), {
message: /"request" must be an instance of SendSMSRequest/,
});
t.falsy((t.context.client.request.post as SinonStub).calledWith(`${RegionUS.apiUrl}/send/sms`));
});

test('#sendSMS: with template: success', (t) => {
sinon.stub(t.context.client.request, 'post');
let req = new SendSMSRequest({
to: '+1234567890',
identifiers: { id: '2' },
transactional_message_id: 1,
});
t.context.client.sendSMS(req);
t.truthy((t.context.client.request.post as SinonStub).calledWith(`${RegionUS.apiUrl}/send/sms`, req.message));
t.is(req.message.transactional_message_id, 1);
t.is(req.message.to, '+1234567890');
});

test('#sendSMS: with optional parameters: success', (t) => {
sinon.stub(t.context.client.request, 'post');
let req = new SendSMSRequest({
to: '+1234567890',
identifiers: { id: '2' },
transactional_message_id: 1,
message_data: { key: 'value' },
disable_message_retention: true,
send_to_unsubscribed: true,
queue_draft: true,
send_at: 1234567890,
language: 'en',
});
t.context.client.sendSMS(req);
t.truthy((t.context.client.request.post as SinonStub).calledWith(`${RegionUS.apiUrl}/send/sms`, req.message));
t.is(req.message.transactional_message_id, 1);
t.is(req.message.to, '+1234567890');
t.deepEqual(req.message.message_data, { key: 'value' });
t.true(req.message.disable_message_retention);
t.true(req.message.send_to_unsubscribed);
t.true(req.message.queue_draft);
t.is(req.message.send_at, 1234567890);
t.is(req.message.language, 'en');
});

test('#sendSMS: error', async (t) => {
sinon.stub(t.context.client.request, 'post').rejects({ message: 'sample error', statusCode: 400 });

let req = new SendSMSRequest({
to: '+1234567890',
identifiers: { id: '2' },
transactional_message_id: 1,
});
t.context.client.sendSMS(req).catch((err) => {
t.is(err.message, 'sample error');
t.is(err.statusCode, 400);
});

t.truthy((t.context.client.request.post as SinonStub).calledWith(`${RegionUS.apiUrl}/send/sms`, req.message));
});