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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## Unreleased

* The `sendEmail` function can now be passed `santiseContentFor` as an optional argument.

## 8.3.2 - 2026-03-24

* Include type files in published package (previous attempt did not work)
Expand Down
18 changes: 12 additions & 6 deletions client/notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,11 @@ function NotifyClient(apiKeyOrUrl, serviceIdOrApiKey, apiKeyId) {
* @param {String} reference
* @param {String} replyToId
* @param {String} oneClickUnsubscribeURL
* @param {String[]} [sanitiseContentFor]
*
* @returns {Object}
*/
function createNotificationPayload(type, templateId, to, personalisation, reference, replyToId, oneClickUnsubscribeURL) {
function createNotificationPayload(type, templateId, to, personalisation, reference, replyToId, oneClickUnsubscribeURL, sanitiseContentFor) {

var payload = {
template_id: templateId
Expand Down Expand Up @@ -116,6 +117,10 @@ function createNotificationPayload(type, templateId, to, personalisation, refere
payload.one_click_unsubscribe_url = oneClickUnsubscribeURL;
}

if (sanitiseContentFor && type == 'email') {
payload.sanitise_content_for = sanitiseContentFor;
}

return payload;
}

Expand Down Expand Up @@ -187,22 +192,23 @@ function _check_and_encode_file(file, size_limit) {
/**
* @param {string} templateId
* @param {string} emailAddress
* @param {{personalisation?: Object, reference?: string, emailReplyToId?: string, oneClickUnsubscribeURL?: string}} [options]
* @returns {Promise<import('axios').AxiosResponse<{id: string, reference?: string, content: {body: string, subject: string, from_email: string, one_click_unsubscribe_url?: string}, uri: string, template: TemplateRef}>>}
* @param {{personalisation?: Object, reference?: string, emailReplyToId?: string, oneClickUnsubscribeURL?: string, sanitiseContentFor?: string[]}} [options]
* @returns {Promise<import('axios').AxiosResponse<{id: string, reference?: string, content: {body: string, subject: string, from_email: string, one_click_unsubscribe_url?: string}, sanitised_content: Record<string, Record<string, string>>, uri: string, template: TemplateRef}>>}
*/
NotifyClient.prototype.sendEmail = function (templateId, emailAddress, options) {
options = options || {};
var err = checkOptionsKeys(['personalisation', 'reference', 'emailReplyToId', 'oneClickUnsubscribeURL'], options)
var err = checkOptionsKeys(['personalisation', 'reference', 'emailReplyToId', 'oneClickUnsubscribeURL', 'sanitiseContentFor'], options)
if (err) {
return Promise.reject(err);
}
var personalisation = options.personalisation || undefined,
reference = options.reference || undefined,
emailReplyToId = options.emailReplyToId || undefined,
oneClickUnsubscribeURL = options.oneClickUnsubscribeURL || undefined;
oneClickUnsubscribeURL = options.oneClickUnsubscribeURL || undefined,
sanitiseContentFor = options.sanitiseContentFor || undefined;

return this.apiClient.post('/v2/notifications/email',
createNotificationPayload('email', templateId, emailAddress, personalisation, reference, emailReplyToId, oneClickUnsubscribeURL));
createNotificationPayload('email', templateId, emailAddress, personalisation, reference, emailReplyToId, oneClickUnsubscribeURL, sanitiseContentFor));
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,15 @@
"scheduled_for": {"oneOf":[
{"$ref": "definitions.json#/datetime"},
{"type": "null"}
]}
]},
"sanitised_content": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": { "type": "string" }
}
}
},
"additionalProperties": true,
"required": ["id", "content", "uri", "template"]
"required": ["id", "content", "uri", "template", "sanitised_content"]
}
3 changes: 2 additions & 1 deletion spec/integration/schemas/v2/definitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"body": {"type": "string"},
"from_email": {"type": "string", "format": "email_address"},
"subject": {"type": "string"},
"one_click_unsubscribe_url": {"type": ["string", "null"], "format": "uri"}
"one_click_unsubscribe_url": {"type": ["string", "null"], "format": "uri"},
"sanitise_content_for": {"type": "array", "items": {"type": "string"}}
},
"required": ["body", "from_email", "subject"]
},
Expand Down
25 changes: 25 additions & 0 deletions spec/integration/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,31 @@ describer('notification api with a live service', function () {
})
});

it('email notification with sanitise_content_for', () => {
const unsafeInput = 'User, [click here](https://evil.link)';
const expectedSanitised = 'User, \\[click here\\]\\(\\)';

let modified_personalisation = { ...personalisation };
modified_personalisation['name'] = unsafeInput;

var postEmailNotificationResponseJson = require('./schemas/v2/POST_notification_email_response.json'),
options = {
personalisation: modified_personalisation,
reference: clientRef,
sanitiseContentFor: ['name']
};

return notifyClient.sendEmail(emailTemplateId, email, options).then((response) => {
response.status.should.equal(201);
expect(response.data).to.be.jsonSchema(postEmailNotificationResponseJson);
response.data.content.body.should.contain(expectedSanitised);
const field = response.data.sanitised_content.name;
field.sanitised.should.equal(expectedSanitised);
field.unsanitised.should.equal(unsafeInput);
response.data.should.have.property('sanitised_content');
});
});

it('send email notification with document upload', () => {
var postEmailNotificationResponseJson = require('./schemas/v2/POST_notification_email_response.json'),
options = {personalisation: { name: 'Foo', documents:
Expand Down
25 changes: 25 additions & 0 deletions spec/notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,31 @@ describe('notification api', () => {
});
});

it('should send an email with sanitise content parameter', () => {

let email = 'dom@example.com',
templateId = '123',
options = {
personalisation: {code: '12345', name: 'John'},
sanitiseContentFor: ['code']
},
data = {
template_id: templateId,
email_address: email,
personalisation: options.personalisation,
sanitise_content_for: options.sanitiseContentFor
};

notifyAuthNock
.post('/v2/notifications/email', data)
.reply(200, {hooray: 'bkbbk'});

return notifyClient.sendEmail(templateId, email, options)
.then((response) => {
expect(response.status).to.equal(200);
});
});

it('should send an email with document upload', () => {
let email = 'dom@example.com',
templateId = '123',
Expand Down
6 changes: 4 additions & 2 deletions types/client/notification.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,14 +193,15 @@ declare class NotifyClient {
/**
* @param {string} templateId
* @param {string} emailAddress
* @param {{personalisation?: Object, reference?: string, emailReplyToId?: string, oneClickUnsubscribeURL?: string}} [options]
* @returns {Promise<import('axios').AxiosResponse<{id: string, reference?: string, content: {body: string, subject: string, from_email: string, one_click_unsubscribe_url?: string}, uri: string, template: TemplateRef}>>}
* @param {{personalisation?: Object, reference?: string, emailReplyToId?: string, oneClickUnsubscribeURL?: string, sanitiseContentFor?: string[]}} [options]
* @returns {Promise<import('axios').AxiosResponse<{id: string, reference?: string, content: {body: string, subject: string, from_email: string, one_click_unsubscribe_url?: string}, sanitised_content: Record<string, Record<string, string>>, uri: string, template: TemplateRef}>>}
*/
sendEmail(templateId: string, emailAddress: string, options?: {
personalisation?: any;
reference?: string;
emailReplyToId?: string;
oneClickUnsubscribeURL?: string;
sanitiseContentFor?: string[];
}): Promise<import("axios").AxiosResponse<{
id: string;
reference?: string;
Expand All @@ -210,6 +211,7 @@ declare class NotifyClient {
from_email: string;
one_click_unsubscribe_url?: string;
};
sanitised_content: Record<string, Record<string, string>>;
uri: string;
template: TemplateRef;
}>>;
Expand Down