n8n-nodes-tone 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/credentials/ToneApi.credentials.d.ts +22 -0
  2. package/dist/credentials/ToneApi.credentials.d.ts.map +1 -0
  3. package/dist/credentials/ToneApi.credentials.js +44 -0
  4. package/dist/credentials/ToneApi.credentials.js.map +1 -0
  5. package/dist/index.d.ts +6 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +14 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/nodes/ToneMakeCall/ToneMakeCall.node.d.ts +6 -0
  10. package/dist/nodes/ToneMakeCall/ToneMakeCall.node.d.ts.map +1 -0
  11. package/dist/nodes/ToneMakeCall/ToneMakeCall.node.js +110 -0
  12. package/dist/nodes/ToneMakeCall/ToneMakeCall.node.js.map +1 -0
  13. package/dist/nodes/ToneSendSms/ToneSendSms.node.d.ts +6 -0
  14. package/dist/nodes/ToneSendSms/ToneSendSms.node.d.ts.map +1 -0
  15. package/dist/nodes/ToneSendSms/ToneSendSms.node.js +82 -0
  16. package/dist/nodes/ToneSendSms/ToneSendSms.node.js.map +1 -0
  17. package/dist/nodes/ToneStartCampaign/ToneStartCampaign.node.d.ts +6 -0
  18. package/dist/nodes/ToneStartCampaign/ToneStartCampaign.node.d.ts.map +1 -0
  19. package/dist/nodes/ToneStartCampaign/ToneStartCampaign.node.js +61 -0
  20. package/dist/nodes/ToneStartCampaign/ToneStartCampaign.node.js.map +1 -0
  21. package/dist/nodes/ToneWebhook/ToneWebhook.node.d.ts +13 -0
  22. package/dist/nodes/ToneWebhook/ToneWebhook.node.d.ts.map +1 -0
  23. package/dist/nodes/ToneWebhook/ToneWebhook.node.js +129 -0
  24. package/dist/nodes/ToneWebhook/ToneWebhook.node.js.map +1 -0
  25. package/package.json +46 -0
  26. package/src/credentials/ToneApi.credentials.ts +43 -0
  27. package/src/index.ts +5 -0
  28. package/src/nodes/ToneMakeCall/ToneMakeCall.node.ts +119 -0
  29. package/src/nodes/ToneMakeCall/tone.svg +5 -0
  30. package/src/nodes/ToneSendSms/ToneSendSms.node.ts +91 -0
  31. package/src/nodes/ToneSendSms/tone.svg +5 -0
  32. package/src/nodes/ToneStartCampaign/ToneStartCampaign.node.ts +70 -0
  33. package/src/nodes/ToneStartCampaign/tone.svg +5 -0
  34. package/src/nodes/ToneWebhook/ToneWebhook.node.ts +147 -0
  35. package/src/nodes/ToneWebhook/tone.svg +5 -0
@@ -0,0 +1,22 @@
1
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class ToneApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ properties: INodeProperties[];
7
+ authenticate: {
8
+ type: "generic";
9
+ properties: {
10
+ headers: {
11
+ Authorization: string;
12
+ };
13
+ };
14
+ };
15
+ test: {
16
+ request: {
17
+ baseURL: string;
18
+ url: string;
19
+ };
20
+ };
21
+ }
22
+ //# sourceMappingURL=ToneApi.credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToneApi.credentials.d.ts","sourceRoot":"","sources":["../../src/credentials/ToneApi.credentials.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAEpE,qBAAa,OAAQ,YAAW,eAAe;IAC7C,IAAI,SAAY;IAChB,WAAW,SAAa;IACxB,gBAAgB,SAA0C;IAE1D,UAAU,EAAE,eAAe,EAAE,CAkB5B;IAED,YAAY;;;;;;;MAOX;IAED,IAAI;;;;;MAKH;CACF"}
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ToneApi = void 0;
4
+ class ToneApi {
5
+ constructor() {
6
+ this.name = 'toneApi';
7
+ this.displayName = 'Tone API';
8
+ this.documentationUrl = 'https://usetone.ai/docs/api-reference';
9
+ this.properties = [
10
+ {
11
+ displayName: 'API Key',
12
+ name: 'apiKey',
13
+ type: 'string',
14
+ typeOptions: { password: true },
15
+ default: '',
16
+ placeholder: 'tone_live_...',
17
+ description: 'Your Tone API key. Generate one at https://app.usetone.ai/settings/api-keys',
18
+ },
19
+ {
20
+ displayName: 'Base URL',
21
+ name: 'baseUrl',
22
+ type: 'string',
23
+ default: 'https://tone-production-4819.up.railway.app',
24
+ description: 'Tone API base URL. Leave as default unless using a self-hosted instance.',
25
+ },
26
+ ];
27
+ this.authenticate = {
28
+ type: 'generic',
29
+ properties: {
30
+ headers: {
31
+ Authorization: '=Bearer {{$credentials.apiKey}}',
32
+ },
33
+ },
34
+ };
35
+ this.test = {
36
+ request: {
37
+ baseURL: '={{$credentials.baseUrl}}',
38
+ url: '/health',
39
+ },
40
+ };
41
+ }
42
+ }
43
+ exports.ToneApi = ToneApi;
44
+ //# sourceMappingURL=ToneApi.credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToneApi.credentials.js","sourceRoot":"","sources":["../../src/credentials/ToneApi.credentials.ts"],"names":[],"mappings":";;;AAEA,MAAa,OAAO;IAApB;QACE,SAAI,GAAG,SAAS,CAAA;QAChB,gBAAW,GAAG,UAAU,CAAA;QACxB,qBAAgB,GAAG,uCAAuC,CAAA;QAE1D,eAAU,GAAsB;YAC9B;gBACE,WAAW,EAAE,SAAS;gBACtB,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC/B,OAAO,EAAE,EAAE;gBACX,WAAW,EAAE,eAAe;gBAC5B,WAAW,EACT,6EAA6E;aAChF;YACD;gBACE,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,6CAA6C;gBACtD,WAAW,EAAE,0EAA0E;aACxF;SACF,CAAA;QAED,iBAAY,GAAG;YACb,IAAI,EAAE,SAAkB;YACxB,UAAU,EAAE;gBACV,OAAO,EAAE;oBACP,aAAa,EAAE,iCAAiC;iBACjD;aACF;SACF,CAAA;QAED,SAAI,GAAG;YACL,OAAO,EAAE;gBACP,OAAO,EAAE,2BAA2B;gBACpC,GAAG,EAAE,SAAS;aACf;SACF,CAAA;IACH,CAAC;CAAA;AAxCD,0BAwCC"}
@@ -0,0 +1,6 @@
1
+ export { ToneApi } from './credentials/ToneApi.credentials.js';
2
+ export { ToneWebhook } from './nodes/ToneWebhook/ToneWebhook.node.js';
3
+ export { ToneMakeCall } from './nodes/ToneMakeCall/ToneMakeCall.node.js';
4
+ export { ToneSendSms } from './nodes/ToneSendSms/ToneSendSms.node.js';
5
+ export { ToneStartCampaign } from './nodes/ToneStartCampaign/ToneStartCampaign.node.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,sCAAsC,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAA;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAA;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qDAAqD,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ToneStartCampaign = exports.ToneSendSms = exports.ToneMakeCall = exports.ToneWebhook = exports.ToneApi = void 0;
4
+ var ToneApi_credentials_js_1 = require("./credentials/ToneApi.credentials.js");
5
+ Object.defineProperty(exports, "ToneApi", { enumerable: true, get: function () { return ToneApi_credentials_js_1.ToneApi; } });
6
+ var ToneWebhook_node_js_1 = require("./nodes/ToneWebhook/ToneWebhook.node.js");
7
+ Object.defineProperty(exports, "ToneWebhook", { enumerable: true, get: function () { return ToneWebhook_node_js_1.ToneWebhook; } });
8
+ var ToneMakeCall_node_js_1 = require("./nodes/ToneMakeCall/ToneMakeCall.node.js");
9
+ Object.defineProperty(exports, "ToneMakeCall", { enumerable: true, get: function () { return ToneMakeCall_node_js_1.ToneMakeCall; } });
10
+ var ToneSendSms_node_js_1 = require("./nodes/ToneSendSms/ToneSendSms.node.js");
11
+ Object.defineProperty(exports, "ToneSendSms", { enumerable: true, get: function () { return ToneSendSms_node_js_1.ToneSendSms; } });
12
+ var ToneStartCampaign_node_js_1 = require("./nodes/ToneStartCampaign/ToneStartCampaign.node.js");
13
+ Object.defineProperty(exports, "ToneStartCampaign", { enumerable: true, get: function () { return ToneStartCampaign_node_js_1.ToneStartCampaign; } });
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+EAA8D;AAArD,iHAAA,OAAO,OAAA;AAChB,+EAAqE;AAA5D,kHAAA,WAAW,OAAA;AACpB,kFAAwE;AAA/D,oHAAA,YAAY,OAAA;AACrB,+EAAqE;AAA5D,kHAAA,WAAW,OAAA;AACpB,iGAAuF;AAA9E,8HAAA,iBAAiB,OAAA"}
@@ -0,0 +1,6 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class ToneMakeCall implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
6
+ //# sourceMappingURL=ToneMakeCall.node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToneMakeCall.node.d.ts","sourceRoot":"","sources":["../../../src/nodes/ToneMakeCall/ToneMakeCall.node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EAEjB,kBAAkB,EAClB,SAAS,EACT,oBAAoB,EACrB,MAAM,cAAc,CAAA;AAGrB,qBAAa,YAAa,YAAW,SAAS;IAC5C,WAAW,EAAE,oBAAoB,CA0DhC;IAEK,OAAO,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;CAgDxE"}
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ToneMakeCall = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ class ToneMakeCall {
6
+ constructor() {
7
+ this.description = {
8
+ displayName: 'Tone: Make Call',
9
+ name: 'toneMakeCall',
10
+ icon: 'file:tone.svg',
11
+ group: ['transform'],
12
+ version: 1,
13
+ description: 'Initiate an outbound AI phone call via Tone',
14
+ defaults: { name: 'Tone Make Call' },
15
+ inputs: ['main'],
16
+ outputs: ['main'],
17
+ credentials: [{ name: 'toneApi', required: true }],
18
+ properties: [
19
+ {
20
+ displayName: 'To (Phone Number)',
21
+ name: 'phoneNumber',
22
+ type: 'string',
23
+ default: '',
24
+ placeholder: '+919876543210',
25
+ description: 'Destination phone number in E.164 format',
26
+ required: true,
27
+ },
28
+ {
29
+ displayName: 'From (Caller ID)',
30
+ name: 'callerId',
31
+ type: 'string',
32
+ default: '',
33
+ placeholder: '+918035001234',
34
+ description: 'Your provisioned Tone number in E.164 format',
35
+ required: true,
36
+ },
37
+ {
38
+ displayName: 'Webhook URL',
39
+ name: 'webhookUrl',
40
+ type: 'string',
41
+ default: '',
42
+ description: 'Optional URL to receive call events (call.turn, call.completed)',
43
+ },
44
+ {
45
+ displayName: 'System Prompt / Instructions',
46
+ name: 'systemPrompt',
47
+ type: 'string',
48
+ typeOptions: { rows: 4 },
49
+ default: '',
50
+ description: 'Instructions for the AI voice agent on this call (overrides number-level instructions)',
51
+ },
52
+ {
53
+ displayName: 'Call Type',
54
+ name: 'callType',
55
+ type: 'options',
56
+ options: [
57
+ { name: 'Transactional', value: 'TRANSACTIONAL', description: 'TRAI-exempt — no DND or time restriction' },
58
+ { name: 'Promotional', value: 'PROMOTIONAL', description: 'Subject to DND check and 9 AM–9 PM IST calling hours' },
59
+ { name: 'OTP', value: 'OTP', description: 'One-time password calls — always exempt' },
60
+ ],
61
+ default: 'TRANSACTIONAL',
62
+ },
63
+ ],
64
+ };
65
+ }
66
+ async execute() {
67
+ const items = this.getInputData();
68
+ const results = [];
69
+ const credentials = await this.getCredentials('toneApi');
70
+ const baseUrl = credentials.baseUrl;
71
+ for (let i = 0; i < items.length; i++) {
72
+ const phoneNumber = this.getNodeParameter('phoneNumber', i);
73
+ const callerId = this.getNodeParameter('callerId', i);
74
+ const webhookUrl = this.getNodeParameter('webhookUrl', i);
75
+ const systemPrompt = this.getNodeParameter('systemPrompt', i);
76
+ const callType = this.getNodeParameter('callType', i);
77
+ const body = {
78
+ to: phoneNumber,
79
+ from: callerId,
80
+ callType,
81
+ };
82
+ if (webhookUrl)
83
+ body.webhookUrl = webhookUrl;
84
+ if (systemPrompt)
85
+ body.instructions = systemPrompt;
86
+ try {
87
+ const response = await this.helpers.request({
88
+ method: 'POST',
89
+ url: `${baseUrl}/v1/calls`,
90
+ headers: { Authorization: `Bearer ${credentials.apiKey}` },
91
+ body,
92
+ json: true,
93
+ });
94
+ results.push({ json: response, pairedItem: { item: i } });
95
+ }
96
+ catch (err) {
97
+ if (this.continueOnFail()) {
98
+ const msg = err instanceof Error ? err.message : String(err);
99
+ results.push({ json: { error: msg }, pairedItem: { item: i } });
100
+ }
101
+ else {
102
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), err instanceof Error ? err : new Error(String(err)), { itemIndex: i });
103
+ }
104
+ }
105
+ }
106
+ return [results];
107
+ }
108
+ }
109
+ exports.ToneMakeCall = ToneMakeCall;
110
+ //# sourceMappingURL=ToneMakeCall.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToneMakeCall.node.js","sourceRoot":"","sources":["../../../src/nodes/ToneMakeCall/ToneMakeCall.node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class ToneSendSms implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
6
+ //# sourceMappingURL=ToneSendSms.node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToneSendSms.node.d.ts","sourceRoot":"","sources":["../../../src/nodes/ToneSendSms/ToneSendSms.node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EAEjB,kBAAkB,EAClB,SAAS,EACT,oBAAoB,EACrB,MAAM,cAAc,CAAA;AAGrB,qBAAa,WAAY,YAAW,SAAS;IAC3C,WAAW,EAAE,oBAAoB,CAwChC;IAEK,OAAO,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;CAsCxE"}
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ToneSendSms = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ class ToneSendSms {
6
+ constructor() {
7
+ this.description = {
8
+ displayName: 'Tone: Send SMS',
9
+ name: 'toneSendSms',
10
+ icon: 'file:tone.svg',
11
+ group: ['transform'],
12
+ version: 1,
13
+ description: 'Send an SMS message via Tone',
14
+ defaults: { name: 'Tone Send SMS' },
15
+ inputs: ['main'],
16
+ outputs: ['main'],
17
+ credentials: [{ name: 'toneApi', required: true }],
18
+ properties: [
19
+ {
20
+ displayName: 'To (Phone Number)',
21
+ name: 'to',
22
+ type: 'string',
23
+ default: '',
24
+ placeholder: '+919876543210',
25
+ description: 'Recipient phone number in E.164 format',
26
+ required: true,
27
+ },
28
+ {
29
+ displayName: 'From (Sender Number)',
30
+ name: 'from',
31
+ type: 'string',
32
+ default: '',
33
+ placeholder: '+918035001234',
34
+ description: 'Your provisioned Tone number in E.164 format',
35
+ required: true,
36
+ },
37
+ {
38
+ displayName: 'Message',
39
+ name: 'body',
40
+ type: 'string',
41
+ typeOptions: { rows: 3 },
42
+ default: '',
43
+ description: 'SMS body text (max 1600 characters)',
44
+ required: true,
45
+ },
46
+ ],
47
+ };
48
+ }
49
+ async execute() {
50
+ const items = this.getInputData();
51
+ const results = [];
52
+ const credentials = await this.getCredentials('toneApi');
53
+ const baseUrl = credentials.baseUrl;
54
+ for (let i = 0; i < items.length; i++) {
55
+ const to = this.getNodeParameter('to', i);
56
+ const from = this.getNodeParameter('from', i);
57
+ const body = this.getNodeParameter('body', i);
58
+ try {
59
+ const response = await this.helpers.request({
60
+ method: 'POST',
61
+ url: `${baseUrl}/v1/sms/send`,
62
+ headers: { Authorization: `Bearer ${credentials.apiKey}` },
63
+ body: { to, from, message: body },
64
+ json: true,
65
+ });
66
+ results.push({ json: response, pairedItem: { item: i } });
67
+ }
68
+ catch (err) {
69
+ if (this.continueOnFail()) {
70
+ const msg = err instanceof Error ? err.message : String(err);
71
+ results.push({ json: { error: msg }, pairedItem: { item: i } });
72
+ }
73
+ else {
74
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), err instanceof Error ? err : new Error(String(err)), { itemIndex: i });
75
+ }
76
+ }
77
+ }
78
+ return [results];
79
+ }
80
+ }
81
+ exports.ToneSendSms = ToneSendSms;
82
+ //# sourceMappingURL=ToneSendSms.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToneSendSms.node.js","sourceRoot":"","sources":["../../../src/nodes/ToneSendSms/ToneSendSms.node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class ToneStartCampaign implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
6
+ //# sourceMappingURL=ToneStartCampaign.node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToneStartCampaign.node.d.ts","sourceRoot":"","sources":["../../../src/nodes/ToneStartCampaign/ToneStartCampaign.node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EAEjB,kBAAkB,EAClB,SAAS,EACT,oBAAoB,EACrB,MAAM,cAAc,CAAA;AAGrB,qBAAa,iBAAkB,YAAW,SAAS;IACjD,WAAW,EAAE,oBAAoB,CAqBhC;IAEK,OAAO,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;CAoCxE"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ToneStartCampaign = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ class ToneStartCampaign {
6
+ constructor() {
7
+ this.description = {
8
+ displayName: 'Tone: Start Campaign',
9
+ name: 'toneStartCampaign',
10
+ icon: 'file:tone.svg',
11
+ group: ['transform'],
12
+ version: 1,
13
+ description: 'Start (or resume) a Tone outbound call campaign',
14
+ defaults: { name: 'Tone Start Campaign' },
15
+ inputs: ['main'],
16
+ outputs: ['main'],
17
+ credentials: [{ name: 'toneApi', required: true }],
18
+ properties: [
19
+ {
20
+ displayName: 'Campaign ID',
21
+ name: 'campaignId',
22
+ type: 'string',
23
+ default: '',
24
+ description: 'The Tone campaign ID to start. Create campaigns first via POST /v1/campaigns.',
25
+ required: true,
26
+ },
27
+ ],
28
+ };
29
+ }
30
+ async execute() {
31
+ const items = this.getInputData();
32
+ const results = [];
33
+ const credentials = await this.getCredentials('toneApi');
34
+ const baseUrl = credentials.baseUrl;
35
+ for (let i = 0; i < items.length; i++) {
36
+ const campaignId = this.getNodeParameter('campaignId', i);
37
+ try {
38
+ const response = await this.helpers.request({
39
+ method: 'POST',
40
+ url: `${baseUrl}/v1/campaigns/${encodeURIComponent(campaignId)}/start`,
41
+ headers: { Authorization: `Bearer ${credentials.apiKey}` },
42
+ body: {},
43
+ json: true,
44
+ });
45
+ results.push({ json: { campaignId, ...response }, pairedItem: { item: i } });
46
+ }
47
+ catch (err) {
48
+ if (this.continueOnFail()) {
49
+ const msg = err instanceof Error ? err.message : String(err);
50
+ results.push({ json: { error: msg, campaignId }, pairedItem: { item: i } });
51
+ }
52
+ else {
53
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), err instanceof Error ? err : new Error(String(err)), { itemIndex: i });
54
+ }
55
+ }
56
+ }
57
+ return [results];
58
+ }
59
+ }
60
+ exports.ToneStartCampaign = ToneStartCampaign;
61
+ //# sourceMappingURL=ToneStartCampaign.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToneStartCampaign.node.js","sourceRoot":"","sources":["../../../src/nodes/ToneStartCampaign/ToneStartCampaign.node.ts"],"names":[],"mappings":";;;AAOA,+CAAiD;AAEjD,MAAa,iBAAiB;IAA9B;QACE,gBAAW,GAAyB;YAClC,WAAW,EAAE,sBAAsB;YACnC,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE,eAAe;YACrB,KAAK,EAAE,CAAC,WAAW,CAAC;YACpB,OAAO,EAAE,CAAC;YACV,WAAW,EAAE,iDAAiD;YAC9D,QAAQ,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE;YACzC,MAAM,EAAE,CAAC,MAAM,CAAC;YAChB,OAAO,EAAE,CAAC,MAAM,CAAC;YACjB,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YAClD,UAAU,EAAE;gBACV;oBACE,WAAW,EAAE,aAAa;oBAC1B,IAAI,EAAE,YAAY;oBAClB,IAAI,EAAE,QAAQ;oBACd,OAAO,EAAE,EAAE;oBACX,WAAW,EAAE,+EAA+E;oBAC5F,QAAQ,EAAE,IAAI;iBACf;aACF;SACF,CAAA;IAsCH,CAAC;IApCC,KAAK,CAAC,OAAO;QACX,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,EAAE,CAAA;QACjC,MAAM,OAAO,GAAyB,EAAE,CAAA;QAExC,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAA;QACxD,MAAM,OAAO,GAAG,WAAW,CAAC,OAAiB,CAAA;QAE7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,YAAY,EAAE,CAAC,CAAW,CAAA;YAEnE,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC;oBAC1C,MAAM,EAAE,MAAM;oBACd,GAAG,EAAE,GAAG,OAAO,iBAAiB,kBAAkB,CAAC,UAAU,CAAC,QAAQ;oBACtE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,WAAW,CAAC,MAAgB,EAAE,EAAE;oBACpE,IAAI,EAAE,EAAE;oBACR,IAAI,EAAE,IAAI;iBACX,CAAgB,CAAA;gBAEjB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,UAAU,EAAE,GAAG,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;YAC9E,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACtB,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;oBAC1B,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;oBAC5D,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;gBAC7E,CAAC;qBAAM,CAAC;oBACN,MAAM,IAAI,iCAAkB,CAC1B,IAAI,CAAC,OAAO,EAAE,EACd,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EACnD,EAAE,SAAS,EAAE,CAAC,EAAE,CACjB,CAAA;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,CAAA;IAClB,CAAC;CACF;AA5DD,8CA4DC"}
@@ -0,0 +1,13 @@
1
+ import type { IHookFunctions, IWebhookFunctions, INodeType, INodeTypeDescription, IWebhookResponseData } from 'n8n-workflow';
2
+ export declare class ToneWebhook implements INodeType {
3
+ description: INodeTypeDescription;
4
+ webhookMethods: {
5
+ default: {
6
+ checkExists(this: IHookFunctions): Promise<boolean>;
7
+ create(this: IHookFunctions): Promise<boolean>;
8
+ delete(this: IHookFunctions): Promise<boolean>;
9
+ };
10
+ };
11
+ webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
12
+ }
13
+ //# sourceMappingURL=ToneWebhook.node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToneWebhook.node.d.ts","sourceRoot":"","sources":["../../../src/nodes/ToneWebhook/ToneWebhook.node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EAEjB,SAAS,EACT,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,cAAc,CAAA;AAmBrB,qBAAa,WAAY,YAAW,SAAS;IAC3C,WAAW,EAAE,oBAAoB,CA6BhC;IAID,cAAc;;8BAEc,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;yBAiBtC,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;yBAuBjC,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;;MAsBvD;IAIK,OAAO,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAkBtE"}
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ToneWebhook = void 0;
4
+ const TONE_EVENTS = [
5
+ { name: 'call.completed', value: 'call.completed' },
6
+ { name: 'call.initiated', value: 'call.initiated' },
7
+ { name: 'call.transfer.initiated', value: 'call.transfer.initiated' },
8
+ { name: 'call.transfer.connected', value: 'call.transfer.connected' },
9
+ { name: 'call.transfer.failed', value: 'call.transfer.failed' },
10
+ { name: 'call.transfer.completed', value: 'call.transfer.completed' },
11
+ { name: 'campaign.started', value: 'campaign.started' },
12
+ { name: 'campaign.completed', value: 'campaign.completed' },
13
+ { name: 'campaign.contact.answered', value: 'campaign.contact.answered' },
14
+ { name: 'campaign.contact.no_answer', value: 'campaign.contact.no_answer' },
15
+ { name: 'campaign.contact.failed', value: 'campaign.contact.failed' },
16
+ { name: 'sms.received', value: 'sms.received' },
17
+ { name: 'sms.delivered', value: 'sms.delivered' },
18
+ { name: '* (all events)', value: '*' },
19
+ ];
20
+ class ToneWebhook {
21
+ constructor() {
22
+ this.description = {
23
+ displayName: 'Tone Webhook',
24
+ name: 'toneWebhook',
25
+ icon: 'file:tone.svg',
26
+ group: ['trigger'],
27
+ version: 1,
28
+ description: 'Starts the workflow when a Tone call, SMS, or campaign event occurs',
29
+ defaults: { name: 'Tone Webhook' },
30
+ inputs: [],
31
+ outputs: ['main'],
32
+ credentials: [{ name: 'toneApi', required: true }],
33
+ webhooks: [
34
+ {
35
+ name: 'default',
36
+ httpMethod: 'POST',
37
+ responseMode: 'onReceived',
38
+ path: 'tone-webhook',
39
+ },
40
+ ],
41
+ properties: [
42
+ {
43
+ displayName: 'Events',
44
+ name: 'events',
45
+ type: 'multiOptions',
46
+ options: TONE_EVENTS,
47
+ default: ['call.completed'],
48
+ description: 'Which Tone events to listen for',
49
+ },
50
+ ],
51
+ };
52
+ // ── Lifecycle hooks ─────────────────────────────────────────────────────────
53
+ this.webhookMethods = {
54
+ default: {
55
+ async checkExists() {
56
+ // Check if a webhook with this URL is already registered on Tone
57
+ const webhookUrl = this.getNodeWebhookUrl('default');
58
+ const credentials = await this.getCredentials('toneApi');
59
+ const baseUrl = credentials.baseUrl;
60
+ const res = await this.helpers.request({
61
+ method: 'GET',
62
+ url: `${baseUrl}/v1/webhooks`,
63
+ headers: { Authorization: `Bearer ${credentials.apiKey}` },
64
+ json: true,
65
+ });
66
+ const webhooks = (res?.webhooks ?? []);
67
+ return webhooks.some((wh) => wh.url === webhookUrl);
68
+ },
69
+ async create() {
70
+ const webhookUrl = this.getNodeWebhookUrl('default');
71
+ const events = this.getNodeParameter('events');
72
+ const credentials = await this.getCredentials('toneApi');
73
+ const baseUrl = credentials.baseUrl;
74
+ const res = await this.helpers.request({
75
+ method: 'POST',
76
+ url: `${baseUrl}/v1/webhooks`,
77
+ headers: { Authorization: `Bearer ${credentials.apiKey}` },
78
+ body: { url: webhookUrl, events },
79
+ json: true,
80
+ });
81
+ const webhookId = (res?.id ?? res?.webhookId);
82
+ if (webhookId) {
83
+ const nodeStaticData = this.getWorkflowStaticData('node');
84
+ nodeStaticData.webhookId = webhookId;
85
+ }
86
+ return true;
87
+ },
88
+ async delete() {
89
+ const nodeStaticData = this.getWorkflowStaticData('node');
90
+ const webhookId = nodeStaticData.webhookId;
91
+ if (!webhookId)
92
+ return true;
93
+ const credentials = await this.getCredentials('toneApi');
94
+ const baseUrl = credentials.baseUrl;
95
+ try {
96
+ await this.helpers.request({
97
+ method: 'DELETE',
98
+ url: `${baseUrl}/v1/webhooks/${webhookId}`,
99
+ headers: { Authorization: `Bearer ${credentials.apiKey}` },
100
+ json: true,
101
+ });
102
+ delete nodeStaticData.webhookId;
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ return true;
108
+ },
109
+ },
110
+ };
111
+ }
112
+ // ── Incoming webhook handler ────────────────────────────────────────────────
113
+ async webhook() {
114
+ const body = this.getBodyData();
115
+ const events = this.getNodeParameter('events');
116
+ const event = body.event;
117
+ const shouldProcess = events.includes('*') ||
118
+ !event ||
119
+ events.includes(event);
120
+ if (!shouldProcess) {
121
+ return { noWebhookResponse: true };
122
+ }
123
+ return {
124
+ workflowData: [this.helpers.returnJsonArray([body])],
125
+ };
126
+ }
127
+ }
128
+ exports.ToneWebhook = ToneWebhook;
129
+ //# sourceMappingURL=ToneWebhook.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToneWebhook.node.js","sourceRoot":"","sources":["../../../src/nodes/ToneWebhook/ToneWebhook.node.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "n8n-nodes-tone",
3
+ "version": "1.0.2",
4
+ "description": "n8n community nodes for Tone — provision numbers, send SMS, initiate calls, run campaigns",
5
+ "keywords": ["n8n-community-node-package", "tone", "telephony", "sms", "voice", "ai"],
6
+ "license": "MIT",
7
+ "homepage": "https://usetone.ai",
8
+ "author": {
9
+ "name": "Bellatech Marketing Pvt. Ltd.",
10
+ "email": "support@usetone.ai"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/Bellatech-Marketing-Pvt-Ltd/Tone.git",
15
+ "directory": "packages/n8n-nodes-tone"
16
+ },
17
+ "main": "dist/index.js",
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "typecheck": "tsc --noEmit",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "src"
26
+ ],
27
+ "n8n": {
28
+ "n8nNodesApiVersion": 1,
29
+ "credentials": [
30
+ "dist/credentials/ToneApi.credentials.js"
31
+ ],
32
+ "nodes": [
33
+ "dist/nodes/ToneWebhook/ToneWebhook.node.js",
34
+ "dist/nodes/ToneMakeCall/ToneMakeCall.node.js",
35
+ "dist/nodes/ToneSendSms/ToneSendSms.node.js",
36
+ "dist/nodes/ToneStartCampaign/ToneStartCampaign.node.js"
37
+ ]
38
+ },
39
+ "devDependencies": {
40
+ "n8n-workflow": "^1.82.0",
41
+ "typescript": "^5.8.3"
42
+ },
43
+ "peerDependencies": {
44
+ "n8n-workflow": ">=1.0.0"
45
+ }
46
+ }
@@ -0,0 +1,43 @@
1
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow'
2
+
3
+ export class ToneApi implements ICredentialType {
4
+ name = 'toneApi'
5
+ displayName = 'Tone API'
6
+ documentationUrl = 'https://usetone.ai/docs/api-reference'
7
+
8
+ properties: INodeProperties[] = [
9
+ {
10
+ displayName: 'API Key',
11
+ name: 'apiKey',
12
+ type: 'string',
13
+ typeOptions: { password: true },
14
+ default: '',
15
+ placeholder: 'tone_live_...',
16
+ description:
17
+ 'Your Tone API key. Generate one at https://app.usetone.ai/settings/api-keys',
18
+ },
19
+ {
20
+ displayName: 'Base URL',
21
+ name: 'baseUrl',
22
+ type: 'string',
23
+ default: 'https://tone-production-4819.up.railway.app',
24
+ description: 'Tone API base URL. Leave as default unless using a self-hosted instance.',
25
+ },
26
+ ]
27
+
28
+ authenticate = {
29
+ type: 'generic' as const,
30
+ properties: {
31
+ headers: {
32
+ Authorization: '=Bearer {{$credentials.apiKey}}',
33
+ },
34
+ },
35
+ }
36
+
37
+ test = {
38
+ request: {
39
+ baseURL: '={{$credentials.baseUrl}}',
40
+ url: '/health',
41
+ },
42
+ }
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { ToneApi } from './credentials/ToneApi.credentials.js'
2
+ export { ToneWebhook } from './nodes/ToneWebhook/ToneWebhook.node.js'
3
+ export { ToneMakeCall } from './nodes/ToneMakeCall/ToneMakeCall.node.js'
4
+ export { ToneSendSms } from './nodes/ToneSendSms/ToneSendSms.node.js'
5
+ export { ToneStartCampaign } from './nodes/ToneStartCampaign/ToneStartCampaign.node.js'
@@ -0,0 +1,119 @@
1
+ import type {
2
+ IExecuteFunctions,
3
+ IDataObject,
4
+ INodeExecutionData,
5
+ INodeType,
6
+ INodeTypeDescription,
7
+ } from 'n8n-workflow'
8
+ import { NodeOperationError } from 'n8n-workflow'
9
+
10
+ export class ToneMakeCall implements INodeType {
11
+ description: INodeTypeDescription = {
12
+ displayName: 'Tone: Make Call',
13
+ name: 'toneMakeCall',
14
+ icon: 'file:tone.svg',
15
+ group: ['transform'],
16
+ version: 1,
17
+ description: 'Initiate an outbound AI phone call via Tone',
18
+ defaults: { name: 'Tone Make Call' },
19
+ inputs: ['main'],
20
+ outputs: ['main'],
21
+ credentials: [{ name: 'toneApi', required: true }],
22
+ properties: [
23
+ {
24
+ displayName: 'To (Phone Number)',
25
+ name: 'phoneNumber',
26
+ type: 'string',
27
+ default: '',
28
+ placeholder: '+919876543210',
29
+ description: 'Destination phone number in E.164 format',
30
+ required: true,
31
+ },
32
+ {
33
+ displayName: 'From (Caller ID)',
34
+ name: 'callerId',
35
+ type: 'string',
36
+ default: '',
37
+ placeholder: '+918035001234',
38
+ description: 'Your provisioned Tone number in E.164 format',
39
+ required: true,
40
+ },
41
+ {
42
+ displayName: 'Webhook URL',
43
+ name: 'webhookUrl',
44
+ type: 'string',
45
+ default: '',
46
+ description: 'Optional URL to receive call events (call.turn, call.completed)',
47
+ },
48
+ {
49
+ displayName: 'System Prompt / Instructions',
50
+ name: 'systemPrompt',
51
+ type: 'string',
52
+ typeOptions: { rows: 4 },
53
+ default: '',
54
+ description:
55
+ 'Instructions for the AI voice agent on this call (overrides number-level instructions)',
56
+ },
57
+ {
58
+ displayName: 'Call Type',
59
+ name: 'callType',
60
+ type: 'options',
61
+ options: [
62
+ { name: 'Transactional', value: 'TRANSACTIONAL', description: 'TRAI-exempt — no DND or time restriction' },
63
+ { name: 'Promotional', value: 'PROMOTIONAL', description: 'Subject to DND check and 9 AM–9 PM IST calling hours' },
64
+ { name: 'OTP', value: 'OTP', description: 'One-time password calls — always exempt' },
65
+ ],
66
+ default: 'TRANSACTIONAL',
67
+ },
68
+ ],
69
+ }
70
+
71
+ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
72
+ const items = this.getInputData()
73
+ const results: INodeExecutionData[] = []
74
+
75
+ const credentials = await this.getCredentials('toneApi')
76
+ const baseUrl = credentials.baseUrl as string
77
+
78
+ for (let i = 0; i < items.length; i++) {
79
+ const phoneNumber = this.getNodeParameter('phoneNumber', i) as string
80
+ const callerId = this.getNodeParameter('callerId', i) as string
81
+ const webhookUrl = this.getNodeParameter('webhookUrl', i) as string
82
+ const systemPrompt = this.getNodeParameter('systemPrompt', i) as string
83
+ const callType = this.getNodeParameter('callType', i) as string
84
+
85
+ const body: IDataObject = {
86
+ to: phoneNumber,
87
+ from: callerId,
88
+ callType,
89
+ }
90
+ if (webhookUrl) body.webhookUrl = webhookUrl
91
+ if (systemPrompt) body.instructions = systemPrompt
92
+
93
+ try {
94
+ const response = await this.helpers.request({
95
+ method: 'POST',
96
+ url: `${baseUrl}/v1/calls`,
97
+ headers: { Authorization: `Bearer ${credentials.apiKey as string}` },
98
+ body,
99
+ json: true,
100
+ }) as IDataObject
101
+
102
+ results.push({ json: response, pairedItem: { item: i } })
103
+ } catch (err: unknown) {
104
+ if (this.continueOnFail()) {
105
+ const msg = err instanceof Error ? err.message : String(err)
106
+ results.push({ json: { error: msg }, pairedItem: { item: i } })
107
+ } else {
108
+ throw new NodeOperationError(
109
+ this.getNode(),
110
+ err instanceof Error ? err : new Error(String(err)),
111
+ { itemIndex: i }
112
+ )
113
+ }
114
+ }
115
+ }
116
+
117
+ return [results]
118
+ }
119
+ }
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" fill="none">
2
+ <rect width="60" height="60" rx="12" fill="#6366F1"/>
3
+ <path d="M18 38 C18 26 26 18 30 18 C34 18 42 26 42 38" stroke="white" stroke-width="3.5" stroke-linecap="round" fill="none"/>
4
+ <circle cx="30" cy="38" r="4" fill="white"/>
5
+ </svg>
@@ -0,0 +1,91 @@
1
+ import type {
2
+ IExecuteFunctions,
3
+ IDataObject,
4
+ INodeExecutionData,
5
+ INodeType,
6
+ INodeTypeDescription,
7
+ } from 'n8n-workflow'
8
+ import { NodeOperationError } from 'n8n-workflow'
9
+
10
+ export class ToneSendSms implements INodeType {
11
+ description: INodeTypeDescription = {
12
+ displayName: 'Tone: Send SMS',
13
+ name: 'toneSendSms',
14
+ icon: 'file:tone.svg',
15
+ group: ['transform'],
16
+ version: 1,
17
+ description: 'Send an SMS message via Tone',
18
+ defaults: { name: 'Tone Send SMS' },
19
+ inputs: ['main'],
20
+ outputs: ['main'],
21
+ credentials: [{ name: 'toneApi', required: true }],
22
+ properties: [
23
+ {
24
+ displayName: 'To (Phone Number)',
25
+ name: 'to',
26
+ type: 'string',
27
+ default: '',
28
+ placeholder: '+919876543210',
29
+ description: 'Recipient phone number in E.164 format',
30
+ required: true,
31
+ },
32
+ {
33
+ displayName: 'From (Sender Number)',
34
+ name: 'from',
35
+ type: 'string',
36
+ default: '',
37
+ placeholder: '+918035001234',
38
+ description: 'Your provisioned Tone number in E.164 format',
39
+ required: true,
40
+ },
41
+ {
42
+ displayName: 'Message',
43
+ name: 'body',
44
+ type: 'string',
45
+ typeOptions: { rows: 3 },
46
+ default: '',
47
+ description: 'SMS body text (max 1600 characters)',
48
+ required: true,
49
+ },
50
+ ],
51
+ }
52
+
53
+ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
54
+ const items = this.getInputData()
55
+ const results: INodeExecutionData[] = []
56
+
57
+ const credentials = await this.getCredentials('toneApi')
58
+ const baseUrl = credentials.baseUrl as string
59
+
60
+ for (let i = 0; i < items.length; i++) {
61
+ const to = this.getNodeParameter('to', i) as string
62
+ const from = this.getNodeParameter('from', i) as string
63
+ const body = this.getNodeParameter('body', i) as string
64
+
65
+ try {
66
+ const response = await this.helpers.request({
67
+ method: 'POST',
68
+ url: `${baseUrl}/v1/sms/send`,
69
+ headers: { Authorization: `Bearer ${credentials.apiKey as string}` },
70
+ body: { to, from, message: body },
71
+ json: true,
72
+ }) as IDataObject
73
+
74
+ results.push({ json: response, pairedItem: { item: i } })
75
+ } catch (err: unknown) {
76
+ if (this.continueOnFail()) {
77
+ const msg = err instanceof Error ? err.message : String(err)
78
+ results.push({ json: { error: msg }, pairedItem: { item: i } })
79
+ } else {
80
+ throw new NodeOperationError(
81
+ this.getNode(),
82
+ err instanceof Error ? err : new Error(String(err)),
83
+ { itemIndex: i }
84
+ )
85
+ }
86
+ }
87
+ }
88
+
89
+ return [results]
90
+ }
91
+ }
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" fill="none">
2
+ <rect width="60" height="60" rx="12" fill="#6366F1"/>
3
+ <path d="M18 38 C18 26 26 18 30 18 C34 18 42 26 42 38" stroke="white" stroke-width="3.5" stroke-linecap="round" fill="none"/>
4
+ <circle cx="30" cy="38" r="4" fill="white"/>
5
+ </svg>
@@ -0,0 +1,70 @@
1
+ import type {
2
+ IExecuteFunctions,
3
+ IDataObject,
4
+ INodeExecutionData,
5
+ INodeType,
6
+ INodeTypeDescription,
7
+ } from 'n8n-workflow'
8
+ import { NodeOperationError } from 'n8n-workflow'
9
+
10
+ export class ToneStartCampaign implements INodeType {
11
+ description: INodeTypeDescription = {
12
+ displayName: 'Tone: Start Campaign',
13
+ name: 'toneStartCampaign',
14
+ icon: 'file:tone.svg',
15
+ group: ['transform'],
16
+ version: 1,
17
+ description: 'Start (or resume) a Tone outbound call campaign',
18
+ defaults: { name: 'Tone Start Campaign' },
19
+ inputs: ['main'],
20
+ outputs: ['main'],
21
+ credentials: [{ name: 'toneApi', required: true }],
22
+ properties: [
23
+ {
24
+ displayName: 'Campaign ID',
25
+ name: 'campaignId',
26
+ type: 'string',
27
+ default: '',
28
+ description: 'The Tone campaign ID to start. Create campaigns first via POST /v1/campaigns.',
29
+ required: true,
30
+ },
31
+ ],
32
+ }
33
+
34
+ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
35
+ const items = this.getInputData()
36
+ const results: INodeExecutionData[] = []
37
+
38
+ const credentials = await this.getCredentials('toneApi')
39
+ const baseUrl = credentials.baseUrl as string
40
+
41
+ for (let i = 0; i < items.length; i++) {
42
+ const campaignId = this.getNodeParameter('campaignId', i) as string
43
+
44
+ try {
45
+ const response = await this.helpers.request({
46
+ method: 'POST',
47
+ url: `${baseUrl}/v1/campaigns/${encodeURIComponent(campaignId)}/start`,
48
+ headers: { Authorization: `Bearer ${credentials.apiKey as string}` },
49
+ body: {},
50
+ json: true,
51
+ }) as IDataObject
52
+
53
+ results.push({ json: { campaignId, ...response }, pairedItem: { item: i } })
54
+ } catch (err: unknown) {
55
+ if (this.continueOnFail()) {
56
+ const msg = err instanceof Error ? err.message : String(err)
57
+ results.push({ json: { error: msg, campaignId }, pairedItem: { item: i } })
58
+ } else {
59
+ throw new NodeOperationError(
60
+ this.getNode(),
61
+ err instanceof Error ? err : new Error(String(err)),
62
+ { itemIndex: i }
63
+ )
64
+ }
65
+ }
66
+ }
67
+
68
+ return [results]
69
+ }
70
+ }
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" fill="none">
2
+ <rect width="60" height="60" rx="12" fill="#6366F1"/>
3
+ <path d="M18 38 C18 26 26 18 30 18 C34 18 42 26 42 38" stroke="white" stroke-width="3.5" stroke-linecap="round" fill="none"/>
4
+ <circle cx="30" cy="38" r="4" fill="white"/>
5
+ </svg>
@@ -0,0 +1,147 @@
1
+ import type {
2
+ IHookFunctions,
3
+ IWebhookFunctions,
4
+ IDataObject,
5
+ INodeType,
6
+ INodeTypeDescription,
7
+ IWebhookResponseData,
8
+ } from 'n8n-workflow'
9
+
10
+ const TONE_EVENTS = [
11
+ { name: 'call.completed', value: 'call.completed' },
12
+ { name: 'call.initiated', value: 'call.initiated' },
13
+ { name: 'call.transfer.initiated', value: 'call.transfer.initiated' },
14
+ { name: 'call.transfer.connected', value: 'call.transfer.connected' },
15
+ { name: 'call.transfer.failed', value: 'call.transfer.failed' },
16
+ { name: 'call.transfer.completed', value: 'call.transfer.completed' },
17
+ { name: 'campaign.started', value: 'campaign.started' },
18
+ { name: 'campaign.completed', value: 'campaign.completed' },
19
+ { name: 'campaign.contact.answered', value: 'campaign.contact.answered' },
20
+ { name: 'campaign.contact.no_answer', value: 'campaign.contact.no_answer' },
21
+ { name: 'campaign.contact.failed', value: 'campaign.contact.failed' },
22
+ { name: 'sms.received', value: 'sms.received' },
23
+ { name: 'sms.delivered', value: 'sms.delivered' },
24
+ { name: '* (all events)', value: '*' },
25
+ ]
26
+
27
+ export class ToneWebhook implements INodeType {
28
+ description: INodeTypeDescription = {
29
+ displayName: 'Tone Webhook',
30
+ name: 'toneWebhook',
31
+ icon: 'file:tone.svg',
32
+ group: ['trigger'],
33
+ version: 1,
34
+ description: 'Starts the workflow when a Tone call, SMS, or campaign event occurs',
35
+ defaults: { name: 'Tone Webhook' },
36
+ inputs: [],
37
+ outputs: ['main'],
38
+ credentials: [{ name: 'toneApi', required: true }],
39
+ webhooks: [
40
+ {
41
+ name: 'default',
42
+ httpMethod: 'POST',
43
+ responseMode: 'onReceived',
44
+ path: 'tone-webhook',
45
+ },
46
+ ],
47
+ properties: [
48
+ {
49
+ displayName: 'Events',
50
+ name: 'events',
51
+ type: 'multiOptions',
52
+ options: TONE_EVENTS,
53
+ default: ['call.completed'],
54
+ description: 'Which Tone events to listen for',
55
+ },
56
+ ],
57
+ }
58
+
59
+ // ── Lifecycle hooks ─────────────────────────────────────────────────────────
60
+
61
+ webhookMethods = {
62
+ default: {
63
+ async checkExists(this: IHookFunctions): Promise<boolean> {
64
+ // Check if a webhook with this URL is already registered on Tone
65
+ const webhookUrl = this.getNodeWebhookUrl('default')
66
+ const credentials = await this.getCredentials('toneApi')
67
+ const baseUrl = credentials.baseUrl as string
68
+
69
+ const res = await this.helpers.request({
70
+ method: 'GET',
71
+ url: `${baseUrl}/v1/webhooks`,
72
+ headers: { Authorization: `Bearer ${credentials.apiKey as string}` },
73
+ json: true,
74
+ })
75
+
76
+ const webhooks = (res?.webhooks ?? []) as Array<{ url: string; id: string }>
77
+ return webhooks.some((wh) => wh.url === webhookUrl)
78
+ },
79
+
80
+ async create(this: IHookFunctions): Promise<boolean> {
81
+ const webhookUrl = this.getNodeWebhookUrl('default')
82
+ const events = this.getNodeParameter('events') as string[]
83
+ const credentials = await this.getCredentials('toneApi')
84
+ const baseUrl = credentials.baseUrl as string
85
+
86
+ const res = await this.helpers.request({
87
+ method: 'POST',
88
+ url: `${baseUrl}/v1/webhooks`,
89
+ headers: { Authorization: `Bearer ${credentials.apiKey as string}` },
90
+ body: { url: webhookUrl, events },
91
+ json: true,
92
+ })
93
+
94
+ const webhookId = (res?.id ?? res?.webhookId) as string | undefined
95
+ if (webhookId) {
96
+ const nodeStaticData = this.getWorkflowStaticData('node')
97
+ nodeStaticData.webhookId = webhookId
98
+ }
99
+
100
+ return true
101
+ },
102
+
103
+ async delete(this: IHookFunctions): Promise<boolean> {
104
+ const nodeStaticData = this.getWorkflowStaticData('node')
105
+ const webhookId = nodeStaticData.webhookId as string | undefined
106
+ if (!webhookId) return true
107
+
108
+ const credentials = await this.getCredentials('toneApi')
109
+ const baseUrl = credentials.baseUrl as string
110
+
111
+ try {
112
+ await this.helpers.request({
113
+ method: 'DELETE',
114
+ url: `${baseUrl}/v1/webhooks/${webhookId}`,
115
+ headers: { Authorization: `Bearer ${credentials.apiKey as string}` },
116
+ json: true,
117
+ })
118
+ delete nodeStaticData.webhookId
119
+ } catch {
120
+ return false
121
+ }
122
+ return true
123
+ },
124
+ },
125
+ }
126
+
127
+ // ── Incoming webhook handler ────────────────────────────────────────────────
128
+
129
+ async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
130
+ const body = this.getBodyData() as IDataObject
131
+ const events = this.getNodeParameter('events') as string[]
132
+
133
+ const event = body.event as string | undefined
134
+ const shouldProcess =
135
+ events.includes('*') ||
136
+ !event ||
137
+ events.includes(event)
138
+
139
+ if (!shouldProcess) {
140
+ return { noWebhookResponse: true }
141
+ }
142
+
143
+ return {
144
+ workflowData: [this.helpers.returnJsonArray([body])],
145
+ }
146
+ }
147
+ }
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" fill="none">
2
+ <rect width="60" height="60" rx="12" fill="#6366F1"/>
3
+ <path d="M18 38 C18 26 26 18 30 18 C34 18 42 26 42 38" stroke="white" stroke-width="3.5" stroke-linecap="round" fill="none"/>
4
+ <circle cx="30" cy="38" r="4" fill="white"/>
5
+ </svg>