From 9d0d85875e28e5da7132acef3603a24ff8689b42 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sat, 26 Oct 2019 12:58:04 -0400 Subject: [PATCH] Added Mandrill support --- .../credentials/MandrillApi.credentials.ts | 18 + .../nodes/Mandrill/GenericFunctions.ts | 69 ++++ .../nodes/Mandrill/Mandrill.node.ts | 322 ++++++++++++++++++ .../nodes-base/nodes/Mandrill/mandrill.png | Bin 0 -> 1291 bytes packages/nodes-base/package.json | 6 +- 5 files changed, 413 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/credentials/MandrillApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Mandrill/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Mandrill/Mandrill.node.ts create mode 100644 packages/nodes-base/nodes/Mandrill/mandrill.png diff --git a/packages/nodes-base/credentials/MandrillApi.credentials.ts b/packages/nodes-base/credentials/MandrillApi.credentials.ts new file mode 100644 index 0000000000..3a3d2425d7 --- /dev/null +++ b/packages/nodes-base/credentials/MandrillApi.credentials.ts @@ -0,0 +1,18 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class MandrillApi implements ICredentialType { + name = 'mandrillApi'; + displayName = 'Mandrill API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Mandrill/GenericFunctions.ts b/packages/nodes-base/nodes/Mandrill/GenericFunctions.ts new file mode 100644 index 0000000000..f1c526c8a4 --- /dev/null +++ b/packages/nodes-base/nodes/Mandrill/GenericFunctions.ts @@ -0,0 +1,69 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IExecuteSingleFunctions +} from 'n8n-core'; + +import * as _ from 'lodash'; + +export async function mandrillApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resource: string, method: string, action: string, body: any = {}, headers?: object): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('mandrillApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const data = Object.assign({ }, body, { key: credentials.apiKey }) + + const endpoint = 'mandrillapp.com/api/1.0'; + + const options: OptionsWithUri = { + headers: headers, + method, + uri: `https://${endpoint}${resource}${action}.json`, + body: data, + json: true + }; + + try { + return await this.helpers.request!(options); + } catch (error) { + console.error(error); + + const errorMessage = error.response.body.message || error.response.body.Message; + if (error.name === 'Invalid_Key') { + throw new Error('The provided API key is not a valid Mandrill API key'); + } else if (error.name === 'ValidationError') { + throw new Error('The parameters passed to the API call are invalid or not provided when required'); + } else if (error.name === 'GeneralError') { + throw new Error('An unexpected error occurred processing the request. Mandrill developers will be notified.'); + } + + if (errorMessage !== undefined) { + throw errorMessage; + } + throw error.response.body; + } +} + + export function getToEmailArray(toEmail: String): Array { // tslint:disable-line:no-any + let toEmailArray + if (toEmail.split(',').length > 0) { + const array = toEmail.split(',') + toEmailArray = _.map(array, (email) => { + return { + email: email, + type: 'to' + } + }) + } else { + toEmailArray = [{ + email: toEmail, + type: 'to' + }] + } + return toEmailArray + } diff --git a/packages/nodes-base/nodes/Mandrill/Mandrill.node.ts b/packages/nodes-base/nodes/Mandrill/Mandrill.node.ts new file mode 100644 index 0000000000..4140e03bdc --- /dev/null +++ b/packages/nodes-base/nodes/Mandrill/Mandrill.node.ts @@ -0,0 +1,322 @@ +import { + BINARY_ENCODING, + IExecuteSingleFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; +import { mandrillApiRequest, getToEmailArray } from './GenericFunctions'; + +export class Mandrill implements INodeType { + + // TODO + + /* + https://mandrillapp.com/api/docs/messages.JSON.html#method=send-template + + 1 - add url strip qs support + 2 - add subaccount support + 3 - add bcc address support + 4 - add google analytics campaign support + 5 - add tracking domain support + 6 - add signing domain support + 7 - add return path domainsupport + 8 - add ip pool support + 9 - add return path domain support + 10 - add tags support + 11 - add google analytics domains support + 12 - add attachments support + 13 - add metadata support + 14 - add ip pool support + 15 - add send at support + 16 - add recipient metadata + */ + + description: INodeTypeDescription = { + displayName: 'Mandrill', + name: 'mandrill', + icon: 'file:mandrill.png', + group: ['output'], + version: 1, + description: 'Sends an email via Mandrill', + defaults: { + name: 'Mandrill', + color: '#c02428', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'mandrillApi', + required: true, + } + ], + properties: [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + options: [ + { + name: 'Send template', + value: 'sendTemplate', + description: 'Send template', + }, + { + name: 'Send html', + value: 'sendHtml', + description: 'Send Html', + }, + ], + default: 'sendTemplate', + description: 'The operation to perform.', + }, + { + displayName: 'Template', + name: 'template', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTemplates', + }, + displayOptions: { + show: { + operation: [ + 'sendTemplate', + ], + }, + }, + default: '', + options: [], + required: true, + description: 'The template you want to send', + }, + { + displayName: 'HTML', + name: 'html', + type: 'string', + displayOptions: { + show: { + operation: [ + 'sendHtml', + ], + }, + }, + default: '', + typeOptions: { + rows: 5, + }, + options: [], + required: true, + description: 'The html you want to send', + }, + { + displayName: 'From Email', + name: 'fromEmail', + type: 'string', + default: '', + required: true, + placeholder: 'Admin ', + description: 'Email address of the sender optional with name.', + }, + { + displayName: 'To Email', + name: 'toEmail', + type: 'string', + default: '', + required: true, + placeholder: 'info@example.com', + description: 'Email address of the recipient. Multiple ones can be separated by comma.', + }, + { + displayName: 'Subject', + name: 'subject', + type: 'string', + default: '', + placeholder: 'My subject line', + description: 'Subject line of the email.', + }, + { + displayName: 'Merge vars', + name: 'mergeVars', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + rows: 5, + }, + default: '', + placeholder: `mergeVars: [{ + rcpt: 'example@example.com', + vars: [ + { name: 'name', content: 'content' } + ] + }]`, + description: 'Per-recipient merge variables', + }, + { + displayName: 'Important', + name: 'important', + type: 'boolean', + default: false, + description: 'whether or not this message is important, and should be delivered ahead of non-important messages', + }, + { + displayName: 'Track opens', + name: 'trackOpens', + type: 'boolean', + default: false, + description: 'whether or not to turn on open tracking for the message', + }, + { + displayName: 'Track clicks', + name: 'trackClicks', + type: 'boolean', + default: false, + description: 'whether or not to turn on click tracking for the message', + }, + { + displayName: 'Auto text', + name: 'autoText', + type: 'boolean', + default: false, + description: 'whether or not to automatically generate a text part for messages that are not given text', + }, + { + displayName: 'Auto HTML', + name: 'autoHtml', + type: 'boolean', + default: false, + description: 'whether or not to automatically generate an HTML part for messages that are not given HTML', + }, + { + displayName: 'Inline css', + name: 'inlineCss', + type: 'boolean', + default: false, + description: 'whether or not to automatically inline all CSS styles provided in the message HTML - only for HTML documents less than 256KB in size', + }, + { + displayName: 'Url strip qs', + name: 'urlStripQs', + type: 'boolean', + default: false, + description: 'whether or not to strip the query string from URLs when aggregating tracked URL data', + }, + { + displayName: 'Preserve recipients', + name: 'preserveRecipients', + type: 'boolean', + default: false, + description: 'whether or not to expose all recipients in to "To" header for each email', + }, + { + displayName: 'View content link', + name: 'viewContentLink', + type: 'boolean', + default: false, + description: 'set to false to remove content logging for sensitive emails', + }, + { + displayName: 'Async', + name: 'async', + type: 'boolean', + default: false, + description: `enable a background sending mode that is optimized for bulk sending. In async mode, messages/send will immediately return a status of "queued" for every recipient. To handle rejections when sending in async mode, set up a webhook for the 'reject' event. Defaults to false for messages with no more than 10 recipients; messages with more than 10 recipients are always sent asynchronously, regardless of the value of async.`, + } + ], + }; + + methods = { + loadOptions: { + // Get all the available templates to display them to user so that he can + // select them easily + async getTemplates(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let templates; + try { + templates = await mandrillApiRequest.call(this, '/templates', 'POST', '/list'); + } catch (err) { + throw new Error(`Mandrill Error: ${err}`); + } + for (const template of templates) { + const templateName = template.name; + const templateSlug = template.slug + + returnData.push({ + name: templateName, + value: templateSlug, + }); + } + + return returnData; + } + }, + }; + + + async executeSingle(this: IExecuteSingleFunctions): Promise { + + const operation = this.getNodeParameter('operation') as string; + const fromEmail = this.getNodeParameter('fromEmail') as string; + const toEmail = this.getNodeParameter('toEmail') as string; + const subject = this.getNodeParameter('subject') as string; + const important = this.getNodeParameter('important') as boolean; + const trackOpens = this.getNodeParameter('trackOpens') as boolean; + const trackClicks = this.getNodeParameter('trackClicks') as boolean; + const autoText = this.getNodeParameter('autoText') as boolean; + const autoHtml = this.getNodeParameter('autoHtml') as boolean; + const inlineCss = this.getNodeParameter('inlineCss') as boolean; + const urlStripQs = this.getNodeParameter('urlStripQs') as boolean; + const preserveRecipients = this.getNodeParameter('preserveRecipients') as boolean; + const viewContentLink = this.getNodeParameter('viewContentLink') as boolean; + const async = this.getNodeParameter('async') as boolean; + const toEmailArray = getToEmailArray(toEmail) + + const credentials = this.getCredentials('mandrillApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const body: IDataObject = { + message: { + subject: subject, + from_email: fromEmail, + to: toEmailArray, + important: important, + track_opens: trackOpens, + track_clicks: trackClicks, + auto_text: autoText, + auto_html: autoHtml, + inline_css: inlineCss, + url_strip_qs: urlStripQs, + preserve_recipients: preserveRecipients, + view_content_link: viewContentLink, + async: async, + } + }; + + if (operation === 'sendTemplate') { + const template = this.getNodeParameter('template') as string; + body.template_name = template + } else if (operation === 'sendHtml') { + const html = this.getNodeParameter('html') as string; + body.html = html + } + + let message; + try { + message = await mandrillApiRequest.call(this, '/messages', 'POST', '/send', body); + } catch (err) { + throw new Error(`Mandrill Error: ${err}`); + } + + return { + json: message, + }; + } +} diff --git a/packages/nodes-base/nodes/Mandrill/mandrill.png b/packages/nodes-base/nodes/Mandrill/mandrill.png new file mode 100644 index 0000000000000000000000000000000000000000..5ebb66e097ddf7ede2f5865d6a879515bf493c5b GIT binary patch literal 1291 zcmV+m1@!ufP)002t}0{{R350Hx30007fP)t-s5{<(X zjl>g<#1oIi7?Z~sl*k&D${Uu-9E`goA0Q*2(Ig-tB_SatAtEIrA|;W&C85$LAs{9p zBPXKND5TXXrq(PaB`hc@ER?-1Dk?B6Eio-FGA}PPFEKPRGc>Q=HH@$|vfnnd;5aNJ zIJDt8H8wmiBRn}dJ-X#RyXHQ-=0CjWKrtjiK0Z-RPEk%!TUS|KT3TORUt~xsW@BV$ zWo2ngD{Eyr>LqOFRL6d zt8_K2bTzDXHLQzhtgEc895AgNFs^kqu5~uAbvCbeHLs6quy;1Fm3FcwGO{W*vUfJK zc{jAOv$nOixE(XP9W%QfG`t=&y&g2bfH=T2Jix!d!9ha7fjPq7>BF?7#Dh7=ysya~ zHp?D2%bsk^9yZL&&CMP+&BM6P!M4uf>Cwr+(b3Y=A2`z=In*CH)gL+6*VoumW7v>9 z+1c9KRcP9qm)hFf+mSrmnUver(A{c#-PqIJ+}_@&rQWQj-rnEfjhf=(TVz ztl9=F-;Y<>u+=>FMd|>FMg~;nwQn+Un!n>g3+)gMF?=jH3* z)9jo-?bW*N*S_u8zwO$^?wvsH(XsB+wC>fn@6W97(y;KKKk&|`^1p@iyLAM@y?A7F0i$i@NrUEi1fMceb>W zVnOR%)@v4PfgaCFE+AIYkQ~$#7HjnOxRzEck7YsCy%F=(k79+_>G3VCj9AbzV$Ni- z7V3#ujS7kd{cP)F7HguOkfqhmD^`_V)_CQESm6x`T3T7Lpt&`^Ww93NiCW2p#DZSN z_Kd}vq$g}?b@Pc;X&Vs{UH&1~z%&<@R!%HvMnshHp2b?MyRo#2VnH_|dMOky-RaJ1 zUsp?>bx-B}&`+8);Q(mUI;p6_#OQ41S3-9}}Sd+R_oi>fp2g+=|o?!lsw zNd{H43$C`Tr{u+=Hip#-5f2*F8}eqItOo!9^~WOOoJY*e{ON{Ai{_OEfKuHN5pl&= zX3oA(UM)%ifVTLrJ35Ejiq#1RyXMFTK3Jt*t& z&`spp+S0+Scf|eCh|{SfW*5~zjIxxaEM@&i>o0R~keVjJ^*8_k002ovPDHLkV1lbH Bp^N|k literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 3ee69a1b9a..872ab2ea4a 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -52,7 +52,8 @@ "dist/credentials/TelegramApi.credentials.js", "dist/credentials/TrelloApi.credentials.js", "dist/credentials/TwilioApi.credentials.js", - "dist/credentials/TypeformApi.credentials.js" + "dist/credentials/TypeformApi.credentials.js", + "dist/credentials/MandrillApi.credentials.js" ], "nodes": [ "dist/nodes/ActiveCampaign/ActiveCampaign.node.js", @@ -108,7 +109,8 @@ "dist/nodes/Typeform/TypeformTrigger.node.js", "dist/nodes/WriteBinaryFile.node.js", "dist/nodes/Webhook.node.js", - "dist/nodes/Xml.node.js" + "dist/nodes/Xml.node.js", + "dist/nodes/Mandrill/Mandrill.node.js" ] }, "devDependencies": {