diff --git a/components/postmark/actions/common.mjs b/components/postmark/actions/common.mjs new file mode 100644 index 0000000000000..348f092ef0eae --- /dev/null +++ b/components/postmark/actions/common.mjs @@ -0,0 +1,136 @@ +import postmark from "../postmark.app.mjs"; + +export default { + props: { + postmark, + fromEmail: { + type: "string", + label: "\"From\" email address", + description: + "The sender email address. Must have a registered and confirmed Sender Signature. To include a name, use the format 'Full Name <sender@domain.com>' for the address.", + }, + toEmail: { + type: "string", + label: "Recipient email address(es)", + description: + "Recipient email address. Multiple addresses are comma separated. Max 50.", + }, + ccEmail: { + type: "string", + label: "CC email address(es)", + description: + "Cc recipient email address. Multiple addresses are comma separated. Max 50.", + optional: true, + }, + bccEmail: { + type: "string", + label: "BCC email address(es)", + description: + "Bcc recipient email address. Multiple addresses are comma separated. Max 50.", + optional: true, + }, + tag: { + type: "string", + label: "Tag", + description: + "Email tag that allows you to categorize outgoing emails and get detailed statistics.", + optional: true, + }, + replyTo: { + type: "string", + label: "\"Reply To\" email address", + description: + "Reply To override email address. Defaults to the Reply To set in the sender signature.", + optional: true, + }, + customHeaders: { + type: "string[]", + label: "Custom Headers", + description: "List of custom headers to include.", + optional: true, + }, + trackOpens: { + type: "boolean", + label: "Track Opens", + description: `Activate open tracking for this email. + \\ + **Note:** the email must have \`HTML Body\` to enable open tracking.`, + optional: true, + }, + trackLinks: { + type: "string", + label: "Track Links", + description: + "Activate link tracking for links in the HTML or Text bodies of this email.", + optional: true, + options: [ + "None", + "HtmlAndText", + "HtmlOnly", + "TextOnly", + ], + }, + attachments: { + type: "string[]", + label: "Attachments", + description: `Each attachment should be a string with the parameters separated by a pipe character \`|\`, in the format: \`Name|Content|ContentType\`. Alternatively, you can pass a string representing an object. All three parameters are required: + \\ + \\ + \`Name\` - the filename with extension, i.e. \`readme.txt\` + \\ + \`Content\` - the base64-encoded string with the binary data for the file, i.e. \`dGVzdCBjb250ZW50\` + \\ + \`ContentType\` - the MIME content type, i.e. \`text/plain\` + \\ + \\ + Example with pipe-separated parameters: \`readme.txt|dGVzdCBjb250ZW50|text/plain\` + \\ + Example with JSON-stringified object: \`{"Name":"readme.txt","Content":"dGVzdCBjb250ZW50","ContentType":"text/plain"}\` + `, + optional: true, + }, + metadata: { + type: "object", + label: "Metadata", + description: "Custom metadata key/value pairs.", + optional: true, + }, + messageStream: { + type: "string", + label: "Message stream", + description: + "Set message stream ID that's used for sending. If not provided, message will default to the outbound transactional stream.", + optional: true, + }, + }, + methods: { + getActionRequestCommonData() { + return { + From: this.fromEmail, + To: this.toEmail, + Cc: this.ccEmail, + Bcc: this.bccEmail, + Tag: this.tag, + ReplyTo: this.replyTo, + Headers: this.customHeaders, + TrackOpens: this.trackOpens, + TrackLinks: this.trackLinks, + Attachments: this.getAttachmentData(this.attachments), + Metadata: this.metadata, + MessageStream: this.messageStream, + }; + }, + getAttachmentData(attachments) { + return attachments?.map((str) => { + let params = str.split("|"); + return params.length === 3 + ? { + Name: params[0], + Content: params[1], + ContentType: params[2], + } + : JSON.parse(str); + }); + }, + }, +}; diff --git a/components/postmark/actions/send-email-with-template/send-email-with-template.mjs b/components/postmark/actions/send-email-with-template/send-email-with-template.mjs new file mode 100644 index 0000000000000..74bf534a6aef8 --- /dev/null +++ b/components/postmark/actions/send-email-with-template/send-email-with-template.mjs @@ -0,0 +1,44 @@ +import postmark from "../../postmark.app.mjs"; +import common from "../common.mjs"; + +export default { + ...common, + key: "postmark-send-email-with-template", + name: "Send Email With Template", + description: "Send a single email with Postmark using a template [(See docs here)](https://postmarkapp.com/developer/api/templates-api#email-with-template)", + version: "0.0.1", + type: "action", + props: { + ...common.props, + templateAlias: { + propDefinition: [ + postmark, + "templateAlias", + ], + }, + templateModel: { + type: "object", + label: "Template Model", + description: + "The model to be applied to the specified template to generate the email body and subject.", + }, + inlineCss: { + type: "boolean", + label: "Inline CSS", + description: + "By default, if the specified template contains an HTMLBody, Postmark will apply the style blocks as inline attributes to the rendered HTML content. You may opt-out of this behavior by passing false for this request field.", + optional: true, + }, + }, + async run({ $ }) { + const data = { + ...this.getActionRequestCommonData(), + TemplateAlias: this.templateAlias, + TemplateModel: this.templateModel, + InlineCSS: this.inlineCss, + }; + const response = await this.postmark.sendEmailWithTemplate($, data); + $.export("$summary", "Sent email with template successfully"); + return response; + }, +}; diff --git a/components/postmark/actions/send-single-email/send-single-email.mjs b/components/postmark/actions/send-single-email/send-single-email.mjs index ecae073901e74..d62efd7d4c991 100644 --- a/components/postmark/actions/send-single-email/send-single-email.mjs +++ b/components/postmark/actions/send-single-email/send-single-email.mjs @@ -1,92 +1,50 @@ -// legacy_hash_id: a_8KiGrJ -import { axios } from "@pipedream/platform"; +import common from "../common.mjs"; export default { + ...common, key: "postmark-send-single-email", - name: "Send an Email with Postmark to a Single Recipient", - description: "Send an Email with Postmark to a Single Recipient", - version: "0.1.1", + name: "Send Single Email", + description: "Send a single email with Postmark [(See docs here)](https://postmarkapp.com/developer/api/email-api#send-a-single-email)", + version: "0.2.0", type: "action", props: { - postmark: { - type: "app", - app: "postmark", - }, - from_email: { - type: "string", - label: "From Email", - }, - to_email: { - type: "string", - label: "To Email", - }, - cc_email: { - type: "string", - label: "CC Email", - optional: true, - }, - bcc_email: { - type: "string", - label: "BCC Email", - optional: true, - }, subject: { type: "string", label: "Subject", + description: "Email subject.", }, - tag: { + htmlBody: { type: "string", - label: "Tag", + label: "HTML Body", + description: + `HTML email message. + \\ + **Required** if no \`Text Body\` is specified. + \\ + **Required** to enable \`Open Tracking\`.`, optional: true, }, - html_body: { - type: "string", - label: "Html Body", - optional: true, - }, - text_body: { + textBody: { type: "string", label: "Text Body", + description: + `Plain text email message. + \\ + **Required** if no \`HTML Body\` is specified.`, optional: true, }, - reply_to: { - type: "string", - label: "Reply To Email", - optional: true, - }, - track_opens: { - type: "boolean", - optional: true, - }, - track_links: { - type: "string", - label: "Track Links", - description: "Activate link tracking for links in the HTML or Text bodies of this email. Possible options: None HtmlAndText HtmlOnly TextOnly", - optional: true, - }, + // The above props are intentionally placed first + ...common.props, }, async run({ $ }) { - return await axios($, { - url: "https://api.postmarkapp.com/email", - headers: { - "X-Postmark-Server-Token": `${this.postmark.$auth.api_key}`, - "Content-Type": "application/json", - "Accept": "application/json", - }, - method: "POST", - data: { - "From": this.from_email, - "To": this.to_email, - "Cc": this.cc_email, - "Bcc": this.bcc_email, - "Subject": this.subject, - "Tag": this.tag, - "HtmlBody": this.html_body, - "TextBody": this.text_body, - "ReplyTo": this.reply_to, - "TrackOpens": this.track_opens, - "TrackLinks": this.track_links, - }, - }); + const data = { + ...this.getActionRequestCommonData(), + Subject: this.subject, + HtmlBody: this.htmlBody, + TextBody: this.textBody, + }; + const response = await this.postmark.sendSingleEmail($, data); + $.export("$summary", "Sent email successfully"); + return response; }, }; diff --git a/components/postmark/postmark.app.mjs b/components/postmark/postmark.app.mjs index d8b192d22a49d..1b5f63128eb3d 100644 --- a/components/postmark/postmark.app.mjs +++ b/components/postmark/postmark.app.mjs @@ -1,11 +1,86 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "postmark", - propDefinitions: {}, + propDefinitions: { + templateAlias: { + type: "string", + label: "Template", + description: "The template to use for this email.", + async options(context) { + let { page } = context; + const data = await this.listTemplates(page++); + const options = + data.Templates?.map((obj) => { + return { + label: obj.Name, + value: obj.Alias, + }; + }) ?? []; + + return { + options, + context: { + page, + }, + }; + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _apikey() { + return this.$auth.api_key; + }, + async listTemplates(page) { + const amountPerPage = 10; + const offset = page * amountPerPage; + + return this.sharedRequest(this, { + endpoint: `templates?Count=${amountPerPage}&Offset=${offset}&TemplateType=Standard`, + method: "GET", + }); + }, + getHeaders() { + return { + "X-Postmark-Server-Token": this._apikey(), + "Content-Type": "application/json", + "Accept": "application/json", + }; + }, + async sharedRequest($, params) { + const { + endpoint, + method, + data, + } = params; + + return axios($, { + url: `https://api.postmarkapp.com/${endpoint}`, + method, + headers: this.getHeaders(), + data, + }); + }, + async sharedActionRequest($, endpoint, data) { + return this.sharedRequest($, { + endpoint, + method: "POST", + data, + }); + }, + async sendSingleEmail($, data) { + return this.sharedActionRequest($, "email", data); + }, + async sendEmailWithTemplate($, data) { + return this.sharedActionRequest($, "email/withTemplate", data); + }, + async setServerInfo(data) { + return this.sharedRequest(this, { + endpoint: "server", + method: "put", + data, + }); }, }, }; diff --git a/components/postmark/sources/common.mjs b/components/postmark/sources/common.mjs new file mode 100644 index 0000000000000..d70c8ec001447 --- /dev/null +++ b/components/postmark/sources/common.mjs @@ -0,0 +1,49 @@ +import postmark from "../postmark.app.mjs"; + +export default { + props: { + postmark, + http: { + type: "$.interface.http", + customResponse: true, + }, + }, + methods: { + getWebhookType() { + throw new Error("Component is missing Webhook type definition"); + }, + getWebhookProps() { + return {}; + }, + }, + hooks: { + async activate() { + return this.postmark.setServerInfo({ + [this.getWebhookType()]: this.http.endpoint, + ...this.getWebhookProps(), + }); + }, + async deactivate() { + return this.postmark.setServerInfo({ + [this.getWebhookType()]: "", + }); + }, + }, + async run(data) { + this.http.respond({ + status: 200, + }); + + let dateParam = data.ReceivedAt ?? data.Date ?? Date.now(); + let dateObj = new Date(dateParam); + + let msgId = data.MessageID; + let id = `${msgId}-${dateObj.toISOString()}`; + + this.$emit(data, { + id, + summary: data.Subject, + ts: dateObj.valueOf(), + }); + }, +}; diff --git a/components/postmark/sources/new-email-opened/new-email-opened.mjs b/components/postmark/sources/new-email-opened/new-email-opened.mjs new file mode 100644 index 0000000000000..6f7609043fe5d --- /dev/null +++ b/components/postmark/sources/new-email-opened/new-email-opened.mjs @@ -0,0 +1,41 @@ +import common from "../common.mjs"; + +export default { + ...common, + key: "postmark-new-email-opened", + name: "New Email Opened", + description: + "Emit new event when an email is opened by a recipient [(See docs here)](https://postmarkapp.com/developer/webhooks/open-tracking-webhook)", + version: "0.0.1", + type: "source", + props: { + ...common.props, + trackOpensByDefault: { + type: "boolean", + label: "Track opens by default", + description: `If enabled, all emails being sent through this server will have open tracking enabled by default. Otherwise, only emails that have open tracking explicitly set will trigger this event when opened. + \\ + **Note:** only emails with \`HTML Body\` will have open tracking enabled. + `, + }, + postFirstOpenOnly: { + type: "boolean", + label: "Track first open only", + description: `If enabled, an event will only be emitted the first time the recipient opens the email. + \\ + Otherwise, the event will be emitted every time an open occurs.`, + }, + }, + methods: { + ...common.methods, + getWebhookProps() { + return { + PostFirstOpenOnly: this.postFirstOpenOnly, + TrackOpens: this.trackOpensByDefault, + }; + }, + getWebhookType() { + return "OpenHookUrl"; + }, + }, +}; diff --git a/components/postmark/sources/new-inbound-email-received/new-inbound-email-received.mjs b/components/postmark/sources/new-inbound-email-received/new-inbound-email-received.mjs new file mode 100644 index 0000000000000..ee2af1ef5f3d9 --- /dev/null +++ b/components/postmark/sources/new-inbound-email-received/new-inbound-email-received.mjs @@ -0,0 +1,17 @@ +import common from "../common.mjs"; + +export default { + ...common, + key: "postmark-new-inbound-email-received", + name: "New Inbound Email Received", + description: + "Emit new event when an email is received by the Postmark server [(See docs here)](https://postmarkapp.com/developer/webhooks/inbound-webhook)", + version: "0.0.1", + type: "source", + methods: { + ...common.methods, + getWebhookType() { + return "InboundHookUrl"; + }, + }, +};