diff --git a/packages/nodes-base/credentials/PlivoApi.credentials.ts b/packages/nodes-base/credentials/PlivoApi.credentials.ts
new file mode 100644
index 0000000000..c07162b283
--- /dev/null
+++ b/packages/nodes-base/credentials/PlivoApi.credentials.ts
@@ -0,0 +1,24 @@
+import {
+ ICredentialType,
+ NodePropertyTypes,
+} from 'n8n-workflow';
+
+export class PlivoApi implements ICredentialType {
+ name = 'plivoApi';
+ displayName = 'Plivo API';
+ documentationUrl = 'plivo';
+ properties = [
+ {
+ displayName: 'Auth ID',
+ name: 'authId',
+ type: 'string' as NodePropertyTypes,
+ default: '',
+ },
+ {
+ displayName: 'Auth Token',
+ name: 'authToken',
+ type: 'string' as NodePropertyTypes,
+ default: '',
+ },
+ ];
+}
diff --git a/packages/nodes-base/nodes/Plivo/CallDescription.ts b/packages/nodes-base/nodes/Plivo/CallDescription.ts
new file mode 100644
index 0000000000..7db6e2e19f
--- /dev/null
+++ b/packages/nodes-base/nodes/Plivo/CallDescription.ts
@@ -0,0 +1,117 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+export const callOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'call',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Make',
+ value: 'make',
+ description: 'Make a voice call',
+ },
+ ],
+ default: 'make',
+ description: 'Operation to perform.',
+ },
+] as INodeProperties[];
+
+export const callFields = [
+ // ----------------------------------
+ // call: make
+ // ----------------------------------
+ {
+ displayName: 'From',
+ name: 'from',
+ type: 'string',
+ default: '',
+ placeholder: '+14156667777',
+ description: 'Caller ID for the call to make.',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'call',
+ ],
+ operation: [
+ 'make',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'To',
+ name: 'to',
+ type: 'string',
+ default: '',
+ placeholder: '+14156667778',
+ required: true,
+ description: 'Phone number to make the call to.',
+ displayOptions: {
+ show: {
+ resource: [
+ 'call',
+ ],
+ operation: [
+ 'make',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Answer Method',
+ name: 'answer_method',
+ type: 'options',
+ required: true,
+ description: 'HTTP verb to be used when invoking the Answer URL.',
+ default: 'POST',
+ options: [
+ {
+ name: 'GET',
+ value: 'GET',
+ },
+ {
+ name: 'POST',
+ value: 'POST',
+ },
+ ],
+ displayOptions: {
+ show: {
+ resource: [
+ 'call',
+ ],
+ operation: [
+ 'make',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Answer URL',
+ name: 'answer_url',
+ type: 'string',
+ default: '',
+ description: 'URL to be invoked by Plivo once the call is answered.
It should return the XML to handle the call once answered.',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'call',
+ ],
+ operation: [
+ 'make',
+ ],
+ },
+ },
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Plivo/GenericFunctions.ts b/packages/nodes-base/nodes/Plivo/GenericFunctions.ts
new file mode 100644
index 0000000000..6af3a48acd
--- /dev/null
+++ b/packages/nodes-base/nodes/Plivo/GenericFunctions.ts
@@ -0,0 +1,62 @@
+import {
+ IExecuteFunctions,
+ IHookFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+} from 'n8n-workflow';
+
+/**
+ * Make an API request to Plivo.
+ *
+ * @param {IHookFunctions} this
+ * @param {string} method
+ * @param {string} url
+ * @param {object} body
+ * @returns {Promise}
+ */
+export async function plivoApiRequest(
+ this: IHookFunctions | IExecuteFunctions,
+ method: string,
+ endpoint: string,
+ body: IDataObject = {},
+ qs: IDataObject = {},
+) {
+
+ const credentials = this.getCredentials('plivoApi') as { authId: string, authToken: string };
+
+ if (!credentials) {
+ throw new Error('No credentials returned!');
+ }
+
+ const options = {
+ method,
+ form: body,
+ qs,
+ uri: `https://api.plivo.com/v1/Account/${credentials.authId}${endpoint}/`,
+ auth: {
+ user: credentials.authId,
+ pass: credentials.authToken,
+ },
+ json: true,
+ };
+
+ try {
+ return await this.helpers.request(options);
+ } catch (error) {
+ if (error.statusCode === 401) {
+ throw new Error('Invalid Plivo credentials');
+ }
+ if (error?.response?.body?.error) {
+ let errorMessage = `Plivo error response [${error.statusCode}]: ${error.response.body.error}`;
+ if (error.response.body.more_info) {
+ errorMessage = `errorMessage (${error.response.body.more_info})`;
+ }
+
+ throw new Error(errorMessage);
+ }
+
+ throw error;
+ }
+}
diff --git a/packages/nodes-base/nodes/Plivo/MmsDescription.ts b/packages/nodes-base/nodes/Plivo/MmsDescription.ts
new file mode 100644
index 0000000000..398ae5f182
--- /dev/null
+++ b/packages/nodes-base/nodes/Plivo/MmsDescription.ts
@@ -0,0 +1,107 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+export const mmsOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'mms',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Send',
+ value: 'send',
+ description: 'Send an MMS message (US/Canada only)',
+ },
+ ],
+ default: 'send',
+ description: 'Operation to perform.',
+ },
+] as INodeProperties[];
+
+export const mmsFields = [
+ // ----------------------------------
+ // mms: send
+ // ----------------------------------
+ {
+ displayName: 'From',
+ name: 'from',
+ type: 'string',
+ default: '',
+ description: 'Plivo Number to send the MMS from.',
+ placeholder: '+14156667777',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'mms',
+ ],
+ operation: [
+ 'send',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'To',
+ name: 'to',
+ type: 'string',
+ default: '',
+ description: 'Phone number to send the MMS to.',
+ placeholder: '+14156667778',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'send',
+ ],
+ resource: [
+ 'mms',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ default: '',
+ description: 'Message to send.',
+ required: false,
+ displayOptions: {
+ show: {
+ resource: [
+ 'mms',
+ ],
+ operation: [
+ 'send',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Media URLs',
+ name: 'media_urls',
+ type: 'string',
+ default: '',
+ required: false,
+ displayOptions: {
+ show: {
+ resource: [
+ 'mms',
+ ],
+ operation: [
+ 'send',
+ ],
+ },
+ },
+ description: 'Comma-separated list of media URLs of the files from your file server.',
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Plivo/Plivo.node.json b/packages/nodes-base/nodes/Plivo/Plivo.node.json
new file mode 100644
index 0000000000..64e49874aa
--- /dev/null
+++ b/packages/nodes-base/nodes/Plivo/Plivo.node.json
@@ -0,0 +1,21 @@
+{
+ "node": "n8n-nodes-base.plivo",
+ "nodeVersion": "1.0",
+ "codexVersion": "1.0",
+ "categories": [
+ "Communication",
+ "Development"
+ ],
+ "resources": {
+ "credentialDocumentation": [
+ {
+ "url": "https://docs.n8n.io/credentials/plivo"
+ }
+ ],
+ "primaryDocumentation": [
+ {
+ "url": "https://docs.n8n.io/nodes/n8n-nodes-base.plivo/"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/nodes-base/nodes/Plivo/Plivo.node.ts b/packages/nodes-base/nodes/Plivo/Plivo.node.ts
new file mode 100644
index 0000000000..3436c601f3
--- /dev/null
+++ b/packages/nodes-base/nodes/Plivo/Plivo.node.ts
@@ -0,0 +1,178 @@
+import {
+ IExecuteFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+ INodeExecutionData,
+ INodeType,
+ INodeTypeDescription,
+} from 'n8n-workflow';
+
+import {
+ smsFields,
+ smsOperations,
+} from './SmsDescription';
+
+import {
+ mmsFields,
+ mmsOperations,
+} from './MmsDescription';
+
+import {
+ callFields,
+ callOperations,
+} from './CallDescription';
+
+import {
+ plivoApiRequest,
+} from './GenericFunctions';
+
+export class Plivo implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'Plivo',
+ name: 'plivo',
+ icon: 'file:plivo.svg',
+ group: ['transform'],
+ version: 1,
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
+ description: 'Send SMS/MMS messages or make phone calls',
+ defaults: {
+ name: 'plivo',
+ color: '#43A046',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'plivoApi',
+ required: true,
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Resource',
+ name: 'resource',
+ type: 'options',
+ options: [
+ {
+ name: 'Call',
+ value: 'call',
+ },
+ {
+ name: 'MMS',
+ value: 'mms',
+ },
+ {
+ name: 'SMS',
+ value: 'sms',
+ },
+ ],
+ default: 'sms',
+ required: true,
+ description: 'The resource to operate on.',
+ },
+ ...smsOperations,
+ ...smsFields,
+ ...mmsOperations,
+ ...mmsFields,
+ ...callOperations,
+ ...callFields,
+ ],
+ };
+
+ async execute(this: IExecuteFunctions): Promise {
+ const items = this.getInputData();
+ const returnData: IDataObject[] = [];
+
+ const resource = this.getNodeParameter('resource', 0) as string;
+ const operation = this.getNodeParameter('operation', 0) as string;
+
+ for (let i = 0; i < items.length; i++) {
+
+ let responseData;
+
+ if (resource === 'sms') {
+
+ // *********************************************************************
+ // sms
+ // *********************************************************************
+
+ if (operation === 'send') {
+
+ // ----------------------------------
+ // sms: send
+ // ----------------------------------
+
+ const body = {
+ src: this.getNodeParameter('from', i) as string,
+ dst: this.getNodeParameter('to', i) as string,
+ text: this.getNodeParameter('message', i) as string,
+ } as IDataObject;
+
+ responseData = await plivoApiRequest.call(this, 'POST', '/Message', body);
+
+ }
+
+ } else if (resource === 'call') {
+
+ // *********************************************************************
+ // call
+ // *********************************************************************
+
+ if (operation === 'make') {
+
+ // ----------------------------------
+ // call: make
+ // ----------------------------------
+
+ // https://www.plivo.com/docs/voice/api/call#make-a-call
+
+ const body = {
+ from: this.getNodeParameter('from', i) as string,
+ to: this.getNodeParameter('to', i) as string,
+ answer_url: this.getNodeParameter('answer_url', i) as string,
+ answer_method: this.getNodeParameter('answer_method', i) as string,
+ } as IDataObject;
+
+ responseData = await plivoApiRequest.call(this, 'POST', '/Call', body);
+
+ }
+
+ } else if (resource === 'mms') {
+
+ // *********************************************************************
+ // mms
+ // *********************************************************************
+
+ if (operation === 'send') {
+
+ // ----------------------------------
+ // mss: send
+ // ----------------------------------
+
+ // https://www.plivo.com/docs/sms/api/message#send-a-message
+
+ const body = {
+ src: this.getNodeParameter('from', i) as string,
+ dst: this.getNodeParameter('to', i) as string,
+ text: this.getNodeParameter('message', i) as string,
+ type: 'mms',
+ media_urls: this.getNodeParameter('media_urls', i) as string,
+ } as IDataObject;
+
+ responseData = await plivoApiRequest.call(this, 'POST', '/Message', body);
+
+ }
+
+ }
+
+ Array.isArray(responseData)
+ ? returnData.push(...responseData)
+ : returnData.push(responseData);
+
+ }
+
+ return [this.helpers.returnJsonArray(returnData)];
+ }
+}
diff --git a/packages/nodes-base/nodes/Plivo/SmsDescription.ts b/packages/nodes-base/nodes/Plivo/SmsDescription.ts
new file mode 100644
index 0000000000..ef1774fb93
--- /dev/null
+++ b/packages/nodes-base/nodes/Plivo/SmsDescription.ts
@@ -0,0 +1,89 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+export const smsOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'sms',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Send',
+ value: 'send',
+ description: 'Send an SMS message.',
+ },
+ ],
+ default: 'send',
+ description: 'Operation to perform.',
+ },
+] as INodeProperties[];
+
+export const smsFields = [
+ // ----------------------------------
+ // sms: send
+ // ----------------------------------
+ {
+ displayName: 'From',
+ name: 'from',
+ type: 'string',
+ default: '',
+ description: 'Plivo Number to send the SMS from.',
+ placeholder: '+14156667777',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'sms',
+ ],
+ operation: [
+ 'send',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'To',
+ name: 'to',
+ type: 'string',
+ default: '',
+ description: 'Phone number to send the message to.',
+ placeholder: '+14156667778',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'sms',
+ ],
+ operation: [
+ 'send',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ default: '',
+ description: 'Message to send.',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'send',
+ ],
+ resource: [
+ 'sms',
+ ],
+ },
+ },
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Plivo/plivo.svg b/packages/nodes-base/nodes/Plivo/plivo.svg
new file mode 100644
index 0000000000..445ddb2563
--- /dev/null
+++ b/packages/nodes-base/nodes/Plivo/plivo.svg
@@ -0,0 +1,15 @@
+
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index cafa140270..b4317f1f93 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -178,6 +178,7 @@
"dist/credentials/PipedriveApi.credentials.js",
"dist/credentials/PipedriveOAuth2Api.credentials.js",
"dist/credentials/PhilipsHueOAuth2Api.credentials.js",
+ "dist/credentials/PlivoApi.credentials.js",
"dist/credentials/Postgres.credentials.js",
"dist/credentials/PostHogApi.credentials.js",
"dist/credentials/PostmarkApi.credentials.js",
@@ -444,6 +445,7 @@
"dist/nodes/Pipedrive/Pipedrive.node.js",
"dist/nodes/Pipedrive/PipedriveTrigger.node.js",
"dist/nodes/PhilipsHue/PhilipsHue.node.js",
+ "dist/nodes/Plivo/Plivo.node.js",
"dist/nodes/Postgres/Postgres.node.js",
"dist/nodes/PostHog/PostHog.node.js",
"dist/nodes/Postmark/PostmarkTrigger.node.js",