From ea73453ac3c8b863a0376dcf1fb03acef0f1a571 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Wed, 14 May 2025 16:42:29 -0300 Subject: [PATCH 1/6] mailosaur init --- .../actions/create-email/create-email.mjs | 82 ++++++++ .../actions/delete-email/delete-email.mjs | 29 +++ .../actions/search-email/search-email.mjs | 99 ++++++++++ components/mailosaur/mailosaur.app.mjs | 182 +++++++++++++++++- components/mailosaur/package.json | 2 +- .../new-email-matching-criteria.mjs | 125 ++++++++++++ .../sources/new-message/new-message.mjs | 93 +++++++++ 7 files changed, 606 insertions(+), 6 deletions(-) create mode 100644 components/mailosaur/actions/create-email/create-email.mjs create mode 100644 components/mailosaur/actions/delete-email/delete-email.mjs create mode 100644 components/mailosaur/actions/search-email/search-email.mjs create mode 100644 components/mailosaur/sources/new-email-matching-criteria/new-email-matching-criteria.mjs create mode 100644 components/mailosaur/sources/new-message/new-message.mjs diff --git a/components/mailosaur/actions/create-email/create-email.mjs b/components/mailosaur/actions/create-email/create-email.mjs new file mode 100644 index 0000000000000..7c5a4e4e53fb8 --- /dev/null +++ b/components/mailosaur/actions/create-email/create-email.mjs @@ -0,0 +1,82 @@ +import mailosaur from "../../mailosaur.app.mjs"; + +export default { + key: "mailosaur-create-email", + name: "Create and Send Email via Mailosaur", + description: "Sends an email through Mailosaur. [See the documentation](https://mailosaur.com/docs/api)", + version: "0.0.1", + type: "action", + props: { + mailosaur, + serverId: { + propDefinition: [ + mailosaur, + "serverId", + ], + }, + to: { + propDefinition: [ + mailosaur, + "to", + ], + }, + subject: { + propDefinition: [ + mailosaur, + "subject", + ], + }, + from: { + propDefinition: [ + mailosaur, + "from", + ], + optional: true, + }, + html: { + propDefinition: [ + mailosaur, + "html", + ], + optional: true, + }, + text: { + propDefinition: [ + mailosaur, + "text", + ], + optional: true, + }, + send: { + propDefinition: [ + mailosaur, + "send", + ], + optional: true, + }, + attachments: { + propDefinition: [ + mailosaur, + "attachments", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.mailosaur.sendEmail({ + serverId: this.serverId, + to: this.to, + subject: this.subject, + from: this.from, + html: this.html, + text: this.text, + send: this.send, + attachments: this.attachments + ? this.attachments.map(JSON.parse) + : undefined, + }); + + $.export("$summary", `Email sent successfully to ${this.to}`); + return response; + }, +}; diff --git a/components/mailosaur/actions/delete-email/delete-email.mjs b/components/mailosaur/actions/delete-email/delete-email.mjs new file mode 100644 index 0000000000000..e5f6dd45187e1 --- /dev/null +++ b/components/mailosaur/actions/delete-email/delete-email.mjs @@ -0,0 +1,29 @@ +import mailosaur from "../../mailosaur.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "mailosaur-delete-email", + name: "Delete Email", + description: "Deletes an email from a Mailosaur server using its email ID. [See the documentation](https://mailosaur.com/docs/api)", + version: "0.0.1", + type: "action", + props: { + mailosaur, + emailId: { + propDefinition: [ + mailosaur, + "emailId", + ], + }, + }, + async run({ $ }) { + const response = await this.mailosaur.deleteEmail({ + emailId: this.emailId, + }); + $.export("$summary", `Successfully deleted email with ID ${this.emailId}`); + return { + success: true, + emailId: this.emailId, + }; + }, +}; diff --git a/components/mailosaur/actions/search-email/search-email.mjs b/components/mailosaur/actions/search-email/search-email.mjs new file mode 100644 index 0000000000000..6ec546b0e8c0a --- /dev/null +++ b/components/mailosaur/actions/search-email/search-email.mjs @@ -0,0 +1,99 @@ +import mailosaur from "../../mailosaur.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "mailosaur-search-email", + name: "Search Email", + description: "Search for received emails in a server matching specified criteria. [See the documentation](https://mailosaur.com/docs/api/#search-for-messages)", + version: "0.0.{{ts}}", + type: "action", + props: { + mailosaur, + serverId: { + propDefinition: [ + mailosaur, + "serverId", + ], + }, + receiveAfter: { + propDefinition: [ + mailosaur, + "receiveAfter", + ], + optional: true, + }, + page: { + propDefinition: [ + mailosaur, + "page", + ], + optional: true, + }, + itemsPerPage: { + propDefinition: [ + mailosaur, + "itemsPerPage", + ], + optional: true, + }, + dir: { + propDefinition: [ + mailosaur, + "dir", + ], + optional: true, + }, + sentFrom: { + type: "string", + label: "Sent From", + description: "The full email address from which the target message was sent.", + optional: true, + }, + sentTo: { + type: "string", + label: "Sent To", + description: "The full email address to which the target message was sent.", + optional: true, + }, + subject: { + type: "string", + label: "Subject", + description: "The value to seek within the target email’s subject line.", + optional: true, + }, + body: { + type: "string", + label: "Body", + description: "The value to seek within the target message’s HTML or text body.", + optional: true, + }, + match: { + type: "string", + label: "Match", + description: "If set to `ALL` (default), only results matching all criteria will be returned. If set to `ANY`, results matching any criteria will be returned.", + options: [ + "ALL", + "ANY", + ], + default: "ALL", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.mailosaur.searchMessages({ + serverId: this.serverId, + receiveAfter: this.receiveAfter, + page: this.page, + itemsPerPage: this.itemsPerPage, + dir: this.dir, + sentFrom: this.sentFrom, + sentTo: this.sentTo, + subject: this.subject, + body: this.body, + match: this.match, + }); + + $.export("$summary", `Successfully retrieved ${response.items.length} email(s) from server.`); + return response; + }, +}; diff --git a/components/mailosaur/mailosaur.app.mjs b/components/mailosaur/mailosaur.app.mjs index 5b7f545d2c022..4c70839536037 100644 --- a/components/mailosaur/mailosaur.app.mjs +++ b/components/mailosaur/mailosaur.app.mjs @@ -1,11 +1,183 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "mailosaur", - propDefinitions: {}, + propDefinitions: { + serverId: { + type: "string", + label: "Server ID", + description: "The identifier of the Mailosaur server.", + async options() { + const servers = await this.listServers(); + return servers.map((server) => ({ + label: server.name, + value: server.id, + })); + }, + }, + emailId: { + type: "string", + label: "Email ID", + description: "The identifier of the email to be managed.", + }, + to: { + type: "string", + label: "To", + description: + "The verified external email address to which the email should be sent.", + }, + subject: { + type: "string", + label: "Subject", + description: "The subject line for an email.", + }, + from: { + type: "string", + label: "From", + description: + "Optionally overrides the message’s ‘from’ address. This must be an address ending with `YOUR_SERVER.mailosaur.net`.", + optional: true, + }, + html: { + type: "string", + label: "HTML", + description: "HTML content for the email.", + optional: true, + }, + text: { + type: "string", + label: "Text", + description: "Plain text content for the email.", + optional: true, + }, + send: { + type: "boolean", + label: "Send", + description: + "If not `true`, the email will be created in your server, but will not be sent.", + optional: true, + }, + attachments: { + type: "string[]", + label: "Attachments", + description: + "An object array of base64-encoded attachment objects (`fileName`, `contentType`, `content`).", + optional: true, + }, + receiveAfter: { + type: "string", + label: "Receive After", + description: + "Limits results to only messages received after this date/time.", + optional: true, + }, + page: { + type: "integer", + label: "Page", + description: "Used in conjunction with `itemsPerPage` to support pagination.", + optional: true, + }, + itemsPerPage: { + type: "integer", + label: "Items Per Page", + description: + "A limit on the number of results to be returned per page. Can be set between 1 and 1000 items, default is 50.", + optional: true, + }, + dir: { + type: "string", + label: "Direction", + description: "Optionally limits results based on the direction (`Sent` or `Received`).", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://mailosaur.com/api"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, method = "GET", path = "/", headers, ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method, + url: this._baseUrl() + path, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.api_key}`, + }, + }); + }, + async listServers(opts = {}) { + return this._makeRequest({ + path: "/servers", + ...opts, + }); + }, + async listMessages(opts = {}) { + return this._makeRequest({ + path: "/messages", + ...opts, + }); + }, + async sendEmail(opts = {}) { + const { + serverId, to, subject, from, html, text, send, attachments, + } = opts; + return this._makeRequest({ + method: "POST", + path: `/messages?server=${serverId}`, + data: { + to, + from, + subject, + html, + text, + send, + attachments, + }, + }); + }, + async searchMessages(opts = {}) { + const { + serverId, receiveAfter, page, itemsPerPage, dir, ...criteria + } = opts; + return this._makeRequest({ + method: "POST", + path: `/messages/search?server=${serverId}`, + data: criteria, + params: { + receiveAfter, + page, + itemsPerPage, + dir, + }, + }); + }, + async deleteEmail(opts = {}) { + const { emailId } = opts; + return this._makeRequest({ + method: "DELETE", + path: `/messages/${emailId}`, + }); + }, + async paginate(fn, ...opts) { + let allItems = []; + let currentPage = 1; + let items; + + do { + items = await fn({ + ...opts, + page: currentPage, + }); + allItems = allItems.concat(items.items || items); + currentPage++; + } while (items.length > 0); + + return allItems; }, }, -}; \ No newline at end of file +}; diff --git a/components/mailosaur/package.json b/components/mailosaur/package.json index e06b1c0211a94..a077c7d67e79f 100644 --- a/components/mailosaur/package.json +++ b/components/mailosaur/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/mailosaur/sources/new-email-matching-criteria/new-email-matching-criteria.mjs b/components/mailosaur/sources/new-email-matching-criteria/new-email-matching-criteria.mjs new file mode 100644 index 0000000000000..943885506148c --- /dev/null +++ b/components/mailosaur/sources/new-email-matching-criteria/new-email-matching-criteria.mjs @@ -0,0 +1,125 @@ +import mailosaur from "../../mailosaur.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "mailosaur-new-email-matching-criteria", + name: "New Email Matching Criteria", + description: "Emit new event when an email matching specific criteria is received. [See the documentation](https://mailosaur.com/docs/api)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + mailosaur, + db: "$.service.db", + serverId: { + propDefinition: [ + mailosaur, + "serverId", + ], + }, + sentFrom: { + type: "string", + label: "Sent From", + description: "The email address from which the target message was sent.", + optional: true, + }, + sentTo: { + type: "string", + label: "Sent To", + description: "The email address to which the target message was sent.", + optional: true, + }, + subject: { + type: "string", + label: "Subject", + description: "The subject line to search within the target email.", + optional: true, + }, + body: { + type: "string", + label: "Body", + description: "The value to seek within the target message’s body.", + optional: true, + }, + receiveAfter: { + type: "string", + label: "Receive After", + description: "Limits results to only messages received after this date/time.", + optional: true, + }, + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: 60, + }, + }, + }, + hooks: { + async deploy() { + const criteria = this._buildCriteria(); + const messages = await this.mailosaur.paginate( + this.mailosaur.searchMessages, + { + serverId: this.serverId, + receiveAfter: this.receiveAfter, + ...criteria, + }, + ); + + messages.slice(0, 50).forEach((message) => { + this.$emit(message, { + id: message.id, + summary: `New email from: ${message.from?.[0]?.email}`, + ts: Date.parse(message.received), + }); + }); + }, + }, + methods: { + _getLastMessageId() { + return this.db.get("lastMessageId") || null; + }, + _setLastMessageId(id) { + this.db.set("lastMessageId", id); + }, + _buildCriteria() { + return { + ...(this.sentFrom && { + sentFrom: this.sentFrom, + }), + ...(this.sentTo && { + sentTo: this.sentTo, + }), + ...(this.subject && { + subject: this.subject, + }), + ...(this.body && { + body: this.body, + }), + }; + }, + }, + async run() { + const criteria = this._buildCriteria(); + const messages = await this.mailosaur.paginate( + this.mailosaur.searchMessages, + { + serverId: this.serverId, + receiveAfter: this.receiveAfter, + ...criteria, + }, + ); + + for (const message of messages) { + if (message.id === this._getLastMessageId()) break; + + this.$emit(message, { + id: message.id, + summary: `New email from: ${message.from?.[0]?.email}`, + ts: Date.parse(message.received), + }); + + this._setLastMessageId(message.id); + } + }, +}; diff --git a/components/mailosaur/sources/new-message/new-message.mjs b/components/mailosaur/sources/new-message/new-message.mjs new file mode 100644 index 0000000000000..3c5c3a423cba2 --- /dev/null +++ b/components/mailosaur/sources/new-message/new-message.mjs @@ -0,0 +1,93 @@ +import mailosaur from "../../mailosaur.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "mailosaur-new-message", + name: "New Message Received", + description: "Emit new event when a message is received in a specified Mailosaur inbox. [See the documentation](https://mailosaur.com/docs/api)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + mailosaur, + db: "$.service.db", + serverId: { + propDefinition: [ + mailosaur, + "serverId", + ], + }, + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: 60, + }, + }, + receiveAfter: { + propDefinition: [ + mailosaur, + "receiveAfter", + ], + }, + }, + methods: { + _getLastTimestamp() { + return this.db.get("lastTimestamp"); + }, + _setLastTimestamp(timestamp) { + this.db.set("lastTimestamp", timestamp); + }, + }, + hooks: { + async deploy() { + const receiveAfter = this.receiveAfter || new Date(new Date().setDate(new Date().getDate() - 1)).toISOString(); + const response = await this.mailosaur.searchMessages({ + serverId: this.serverId, + receiveAfter, + itemsPerPage: 50, + }); + + const messages = response.items; + messages.reverse().slice(0, 50) + .forEach((message) => { + this.$emit(message, { + id: message.id, + summary: `New Message: ${message.subject}`, + ts: Date.parse(message.received), + }); + }); + + if (messages.length) { + this._setLastTimestamp(messages[messages.length - 1].received); + } + }, + async activate() { + // Hook for activating the component + }, + async deactivate() { + // Hook for deactivating the component + }, + }, + async run() { + const lastTimestamp = this._getLastTimestamp() || new Date(new Date().setDate(new Date().getDate() - 1)).toISOString(); + + const response = await this.mailosaur.searchMessages({ + serverId: this.serverId, + receiveAfter: lastTimestamp, + itemsPerPage: 50, + }); + + const messages = response.items; + messages.reverse().forEach((message) => { + this.$emit(message, { + id: message.id, + summary: `New Message: ${message.subject}`, + ts: Date.parse(message.received), + }); + }); + + if (messages.length) { + this._setLastTimestamp(messages[messages.length - 1].received); + } + }, +}; From 6a3b772a84e636cc56bb31fa8fb86c9f8fdc4db5 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Thu, 15 May 2025 17:13:14 -0300 Subject: [PATCH 2/6] [Components] mailosaur #16655 Sources - New Message - New Message Macthing Criteira Actions - Create Email - Search Message - Delete Message --- .../actions/create-email/create-email.mjs | 82 ++++---- .../actions/delete-email/delete-email.mjs | 14 +- .../actions/search-email/search-email.mjs | 58 +++--- components/mailosaur/common/constants.mjs | 12 ++ components/mailosaur/mailosaur.app.mjs | 196 ++++++------------ components/mailosaur/package.json | 5 +- components/mailosaur/sources/common/base.mjs | 73 +++++++ .../new-email-matching-criteria.mjs | 117 +++-------- .../test-event.mjs | 16 ++ .../sources/new-message/new-message.mjs | 93 +-------- .../sources/new-message/test-event.mjs | 16 ++ 11 files changed, 307 insertions(+), 375 deletions(-) create mode 100644 components/mailosaur/common/constants.mjs create mode 100644 components/mailosaur/sources/common/base.mjs create mode 100644 components/mailosaur/sources/new-email-matching-criteria/test-event.mjs create mode 100644 components/mailosaur/sources/new-message/test-event.mjs diff --git a/components/mailosaur/actions/create-email/create-email.mjs b/components/mailosaur/actions/create-email/create-email.mjs index 7c5a4e4e53fb8..b7b27a880dbcb 100644 --- a/components/mailosaur/actions/create-email/create-email.mjs +++ b/components/mailosaur/actions/create-email/create-email.mjs @@ -1,3 +1,4 @@ +import { ConfigurationError } from "@pipedream/platform"; import mailosaur from "../../mailosaur.app.mjs"; export default { @@ -15,65 +16,56 @@ export default { ], }, to: { - propDefinition: [ - mailosaur, - "to", - ], - }, - subject: { - propDefinition: [ - mailosaur, - "subject", - ], + type: "string", + label: "To", + description: "The verified external email address to which the email should be sent.", }, from: { - propDefinition: [ - mailosaur, - "from", - ], + type: "string", + label: "From", + description: "Optionally overrides of the message's `from` address. This **must** be an address ending with `YOUR_SERVER.mailosaur.net`, such as `my-emails @a1bcdef2.mailosaur.net`.", optional: true, }, + subject: { + type: "string", + label: "Subject", + description: "The subject line for an email.", + }, html: { - propDefinition: [ - mailosaur, - "html", - ], + type: "object", + label: "HTML", + description: "An object with HTML properties. Please [see the documentation](https://mailosaur.com/docs/api#send-an-email) for more details.", optional: true, }, text: { - propDefinition: [ - mailosaur, - "text", - ], + type: "object", + label: "Text", + description: "An object with Plain text properties. Please [see the documentation](https://mailosaur.com/docs/api#send-an-email) for more details.", optional: true, }, send: { - propDefinition: [ - mailosaur, - "send", - ], - optional: true, - }, - attachments: { - propDefinition: [ - mailosaur, - "attachments", - ], - optional: true, + type: "boolean", + label: "Send", + description: "If `false`, the email will be created in your server, but will not be sent.", }, }, async run({ $ }) { - const response = await this.mailosaur.sendEmail({ - serverId: this.serverId, - to: this.to, - subject: this.subject, - from: this.from, - html: this.html, - text: this.text, - send: this.send, - attachments: this.attachments - ? this.attachments.map(JSON.parse) - : undefined, + if ((!!this.send) && (!this.html && !this.text)) { + throw new ConfigurationError("Please provide either HTML or plain text content."); + } + + const { + mailosaur, + serverId, + ...data + } = this; + + const response = await mailosaur.sendEmail({ + $, + params: { + server: serverId, + }, + data, }); $.export("$summary", `Email sent successfully to ${this.to}`); diff --git a/components/mailosaur/actions/delete-email/delete-email.mjs b/components/mailosaur/actions/delete-email/delete-email.mjs index e5f6dd45187e1..8cf762ac3ba78 100644 --- a/components/mailosaur/actions/delete-email/delete-email.mjs +++ b/components/mailosaur/actions/delete-email/delete-email.mjs @@ -1,5 +1,4 @@ import mailosaur from "../../mailosaur.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "mailosaur-delete-email", @@ -9,20 +8,29 @@ export default { type: "action", props: { mailosaur, + serverId: { + propDefinition: [ + mailosaur, + "serverId", + ], + }, emailId: { propDefinition: [ mailosaur, "emailId", + ({ serverId }) => ({ + serverId, + }), ], }, }, async run({ $ }) { - const response = await this.mailosaur.deleteEmail({ + await this.mailosaur.deleteEmail({ + $, emailId: this.emailId, }); $.export("$summary", `Successfully deleted email with ID ${this.emailId}`); return { - success: true, emailId: this.emailId, }; }, diff --git a/components/mailosaur/actions/search-email/search-email.mjs b/components/mailosaur/actions/search-email/search-email.mjs index 6ec546b0e8c0a..962e2334c22e2 100644 --- a/components/mailosaur/actions/search-email/search-email.mjs +++ b/components/mailosaur/actions/search-email/search-email.mjs @@ -1,11 +1,10 @@ import mailosaur from "../../mailosaur.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "mailosaur-search-email", name: "Search Email", description: "Search for received emails in a server matching specified criteria. [See the documentation](https://mailosaur.com/docs/api/#search-for-messages)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", props: { mailosaur, @@ -16,31 +15,29 @@ export default { ], }, receiveAfter: { - propDefinition: [ - mailosaur, - "receiveAfter", - ], + type: "string", + label: "Receive After", + description: + "Limits results to only messages received after this date/time.", optional: true, }, page: { - propDefinition: [ - mailosaur, - "page", - ], + type: "integer", + label: "Page", + description: "Used in conjunction with `itemsPerPage` to support pagination.", optional: true, }, itemsPerPage: { - propDefinition: [ - mailosaur, - "itemsPerPage", - ], + type: "integer", + label: "Items Per Page", + description: + "A limit on the number of results to be returned per page. Can be set between 1 and 1000 items, default is 50.", optional: true, }, dir: { - propDefinition: [ - mailosaur, - "dir", - ], + type: "string", + label: "Direction", + description: "Optionally limits results based on the direction (`Sent` or `Received`).", optional: true, }, sentFrom: { @@ -81,16 +78,21 @@ export default { }, async run({ $ }) { const response = await this.mailosaur.searchMessages({ - serverId: this.serverId, - receiveAfter: this.receiveAfter, - page: this.page, - itemsPerPage: this.itemsPerPage, - dir: this.dir, - sentFrom: this.sentFrom, - sentTo: this.sentTo, - subject: this.subject, - body: this.body, - match: this.match, + $, + params: { + server: this.serverId, + receiveAfter: this.receiveAfter, + page: this.page, + itemsPerPage: this.itemsPerPage, + dir: this.dir, + }, + data: { + sentFrom: this.sentFrom, + sentTo: this.sentTo, + subject: this.subject, + body: this.body, + match: this.match, + }, }); $.export("$summary", `Successfully retrieved ${response.items.length} email(s) from server.`); diff --git a/components/mailosaur/common/constants.mjs b/components/mailosaur/common/constants.mjs new file mode 100644 index 0000000000000..f396ecd4a72d9 --- /dev/null +++ b/components/mailosaur/common/constants.mjs @@ -0,0 +1,12 @@ +export const LIMIT = 1000; + +export const MATCH_OPTIONS = [ + { + label: "All", + value: "ALL", + }, + { + label: "Any", + value: "ANY", + }, +]; diff --git a/components/mailosaur/mailosaur.app.mjs b/components/mailosaur/mailosaur.app.mjs index 4c70839536037..68780a34084af 100644 --- a/components/mailosaur/mailosaur.app.mjs +++ b/components/mailosaur/mailosaur.app.mjs @@ -1,4 +1,5 @@ import { axios } from "@pipedream/platform"; +import { LIMIT } from "./common/constants.mjs"; export default { type: "app", @@ -7,177 +8,116 @@ export default { serverId: { type: "string", label: "Server ID", - description: "The identifier of the Mailosaur server.", + description: "The identifier of the server from which the email should be sent.", async options() { - const servers = await this.listServers(); - return servers.map((server) => ({ - label: server.name, - value: server.id, + const { items } = await this.listServers(); + + return items.map(({ + id: value, name: label, + }) => ({ + label, + value, })); }, }, emailId: { type: "string", - label: "Email ID", - description: "The identifier of the email to be managed.", - }, - to: { - type: "string", - label: "To", - description: - "The verified external email address to which the email should be sent.", - }, - subject: { - type: "string", - label: "Subject", - description: "The subject line for an email.", - }, - from: { - type: "string", - label: "From", - description: - "Optionally overrides the message’s ‘from’ address. This must be an address ending with `YOUR_SERVER.mailosaur.net`.", - optional: true, - }, - html: { - type: "string", - label: "HTML", - description: "HTML content for the email.", - optional: true, - }, - text: { - type: "string", - label: "Text", - description: "Plain text content for the email.", - optional: true, - }, - send: { - type: "boolean", - label: "Send", - description: - "If not `true`, the email will be created in your server, but will not be sent.", - optional: true, - }, - attachments: { - type: "string[]", - label: "Attachments", - description: - "An object array of base64-encoded attachment objects (`fileName`, `contentType`, `content`).", - optional: true, - }, - receiveAfter: { - type: "string", - label: "Receive After", - description: - "Limits results to only messages received after this date/time.", - optional: true, - }, - page: { - type: "integer", - label: "Page", - description: "Used in conjunction with `itemsPerPage` to support pagination.", - optional: true, - }, - itemsPerPage: { - type: "integer", - label: "Items Per Page", - description: - "A limit on the number of results to be returned per page. Can be set between 1 and 1000 items, default is 50.", - optional: true, - }, - dir: { - type: "string", - label: "Direction", - description: "Optionally limits results based on the direction (`Sent` or `Received`).", - optional: true, + label: "Server ID", + description: "The identifier of the server from which the email should be sent.", + async options({ serverId }) { + const { items } = await this.listMessages({ + params: { + server: serverId, + }, + }); + + return items.map(({ + id: value, subject: label, + }) => ({ + label, + value, + })); + }, }, }, methods: { _baseUrl() { return "https://mailosaur.com/api"; }, - async _makeRequest(opts = {}) { - const { - $ = this, method = "GET", path = "/", headers, ...otherOpts - } = opts; + _auth() { + return { + username: "api", + password: `${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { return axios($, { - ...otherOpts, - method, url: this._baseUrl() + path, - headers: { - ...headers, - Authorization: `Bearer ${this.$auth.api_key}`, - }, + auth: this._auth(), + ...opts, }); }, - async listServers(opts = {}) { + listServers() { return this._makeRequest({ path: "/servers", - ...opts, }); }, - async listMessages(opts = {}) { + listMessages(opts = {}) { return this._makeRequest({ path: "/messages", ...opts, }); }, - async sendEmail(opts = {}) { - const { - serverId, to, subject, from, html, text, send, attachments, - } = opts; + sendEmail(opts = {}) { return this._makeRequest({ method: "POST", - path: `/messages?server=${serverId}`, - data: { - to, - from, - subject, - html, - text, - send, - attachments, - }, + path: "/messages", + ...opts, }); }, - async searchMessages(opts = {}) { - const { - serverId, receiveAfter, page, itemsPerPage, dir, ...criteria - } = opts; + searchMessages(opts = {}) { return this._makeRequest({ method: "POST", - path: `/messages/search?server=${serverId}`, - data: criteria, - params: { - receiveAfter, - page, - itemsPerPage, - dir, - }, + path: "/messages/search", + ...opts, }); }, - async deleteEmail(opts = {}) { - const { emailId } = opts; + deleteEmail({ + emailId, ...opts + }) { return this._makeRequest({ method: "DELETE", path: `/messages/${emailId}`, + ...opts, }); }, - async paginate(fn, ...opts) { - let allItems = []; - let currentPage = 1; - let items; + async *paginate({ + fn, params = {}, maxResults = null, ...opts + }) { + let hasMore = false; + let count = 0; + let page = 0; do { - items = await fn({ + params.page = page++; + params.itemsPerPage = LIMIT; + const { items } = await fn({ + params, ...opts, - page: currentPage, }); - allItems = allItems.concat(items.items || items); - currentPage++; - } while (items.length > 0); + for (const d of items) { + yield d; + + if (maxResults && ++count === maxResults) { + return count; + } + } + + hasMore = items.length; - return allItems; + } while (hasMore); }, }, }; diff --git a/components/mailosaur/package.json b/components/mailosaur/package.json index a077c7d67e79f..766a53deebc63 100644 --- a/components/mailosaur/package.json +++ b/components/mailosaur/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/mailosaur", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Mailosaur Components", "main": "mailosaur.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/mailosaur/sources/common/base.mjs b/components/mailosaur/sources/common/base.mjs new file mode 100644 index 0000000000000..49f1a4ef65efe --- /dev/null +++ b/components/mailosaur/sources/common/base.mjs @@ -0,0 +1,73 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import mailosaur from "../../mailosaur.app.mjs"; + +export default { + props: { + mailosaur, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + serverId: { + propDefinition: [ + mailosaur, + "serverId", + ], + }, + }, + methods: { + _getLastDate() { + return this.db.get("lastDate") || 0; + }, + _setLastDate(lastDate) { + this.db.set("lastDate", lastDate); + }, + getData() { + return {}; + }, + async emitEvent(maxResults = false) { + const lastDate = this._getLastDate(); + + const response = this.mailosaur.paginate({ + fn: this.getFunction(), + receivedAfter: lastDate, + params: { + server: this.serverId, + }, + data: this.getData(), + }); + + let responseArray = []; + for await (const item of response) { + if (Date.parse(item.received) <= Date.parse(lastDate)) break; + responseArray.push(item); + } + + if (responseArray.length) { + if (maxResults && (responseArray.length > maxResults)) { + responseArray.length = maxResults; + } + this._setLastDate(responseArray[0].received); + } + + for (const item of responseArray.reverse()) { + this.$emit(item, { + id: item.id, + summary: this.getSummary(item), + ts: Date.parse(item.received), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/mailosaur/sources/new-email-matching-criteria/new-email-matching-criteria.mjs b/components/mailosaur/sources/new-email-matching-criteria/new-email-matching-criteria.mjs index 943885506148c..4efda8152e56b 100644 --- a/components/mailosaur/sources/new-email-matching-criteria/new-email-matching-criteria.mjs +++ b/components/mailosaur/sources/new-email-matching-criteria/new-email-matching-criteria.mjs @@ -1,125 +1,66 @@ -import mailosaur from "../../mailosaur.app.mjs"; -import { axios } from "@pipedream/platform"; +import { MATCH_OPTIONS } from "../../common/constants.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "mailosaur-new-email-matching-criteria", name: "New Email Matching Criteria", - description: "Emit new event when an email matching specific criteria is received. [See the documentation](https://mailosaur.com/docs/api)", - version: "0.0.{{ts}}", + description: "Emit new event when a message matching specific criteria is received. [See the documentation](https://mailosaur.com/docs/api#search-for-messages)", + version: "0.0.1", type: "source", dedupe: "unique", props: { - mailosaur, - db: "$.service.db", - serverId: { - propDefinition: [ - mailosaur, - "serverId", - ], - }, + ...common.props, sentFrom: { type: "string", label: "Sent From", - description: "The email address from which the target message was sent.", + description: "The full email address or phone number from which the target message was sent.", optional: true, }, sentTo: { type: "string", label: "Sent To", - description: "The email address to which the target message was sent.", + description: "The full email address or phone number to which the target message was sent.", optional: true, }, subject: { type: "string", label: "Subject", - description: "The subject line to search within the target email.", + description: "The value to seek within the target email's subject line.", optional: true, }, body: { type: "string", label: "Body", - description: "The value to seek within the target message’s body.", + description: "The value to seek within the target message's HTML or text body.", optional: true, }, - receiveAfter: { + match: { type: "string", - label: "Receive After", - description: "Limits results to only messages received after this date/time.", + label: "Match", + description: "If set to `ALL` (default), then only results that match all specified criteria will be returned. If set to `ANY`, results that match any of the specified criteria will be returned.", + options: MATCH_OPTIONS, optional: true, }, - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: 60, - }, - }, - }, - hooks: { - async deploy() { - const criteria = this._buildCriteria(); - const messages = await this.mailosaur.paginate( - this.mailosaur.searchMessages, - { - serverId: this.serverId, - receiveAfter: this.receiveAfter, - ...criteria, - }, - ); - - messages.slice(0, 50).forEach((message) => { - this.$emit(message, { - id: message.id, - summary: `New email from: ${message.from?.[0]?.email}`, - ts: Date.parse(message.received), - }); - }); - }, }, methods: { - _getLastMessageId() { - return this.db.get("lastMessageId") || null; - }, - _setLastMessageId(id) { - this.db.set("lastMessageId", id); - }, - _buildCriteria() { + ...common.methods, + getData() { return { - ...(this.sentFrom && { - sentFrom: this.sentFrom, - }), - ...(this.sentTo && { - sentTo: this.sentTo, - }), - ...(this.subject && { - subject: this.subject, - }), - ...(this.body && { - body: this.body, - }), + sentFrom: this.sentFrom, + sentTo: this.sentTo, + subject: this.subject, + body: this.body, + match: this.match, }; }, + getFunction() { + return this.mailosaur.searchMessages; + }, + getSummary(item) { + return `New Message: ${item.subject}`; + }, }, - async run() { - const criteria = this._buildCriteria(); - const messages = await this.mailosaur.paginate( - this.mailosaur.searchMessages, - { - serverId: this.serverId, - receiveAfter: this.receiveAfter, - ...criteria, - }, - ); - - for (const message of messages) { - if (message.id === this._getLastMessageId()) break; - - this.$emit(message, { - id: message.id, - summary: `New email from: ${message.from?.[0]?.email}`, - ts: Date.parse(message.received), - }); - - this._setLastMessageId(message.id); - } - }, + sampleEmit, }; diff --git a/components/mailosaur/sources/new-email-matching-criteria/test-event.mjs b/components/mailosaur/sources/new-email-matching-criteria/test-event.mjs new file mode 100644 index 0000000000000..2a4a1866c8b79 --- /dev/null +++ b/components/mailosaur/sources/new-email-matching-criteria/test-event.mjs @@ -0,0 +1,16 @@ +export default { + "id": "77061c9f-da47-4009-9f33-9715a3bbf00c", + "received": "2019-08-06T17:44:07.197781+00:00", + "type": "Email", + "subject": "Email subject line", + "from": [{ + "name": "Acme", + "email": "noreply@example.com" + }], + "to": [{ + "name": "Jane Doe", + "email": "janedoe@abc1234.mailosaur.net" + }], + "cc": [], + "bcc": [] +} \ No newline at end of file diff --git a/components/mailosaur/sources/new-message/new-message.mjs b/components/mailosaur/sources/new-message/new-message.mjs index 3c5c3a423cba2..2622687f8b9e8 100644 --- a/components/mailosaur/sources/new-message/new-message.mjs +++ b/components/mailosaur/sources/new-message/new-message.mjs @@ -1,93 +1,22 @@ -import mailosaur from "../../mailosaur.app.mjs"; -import { axios } from "@pipedream/platform"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "mailosaur-new-message", name: "New Message Received", - description: "Emit new event when a message is received in a specified Mailosaur inbox. [See the documentation](https://mailosaur.com/docs/api)", - version: "0.0.{{ts}}", + description: "Emit new event when a message is received in a specified Mailosaur inbox. [See the documentation](https://mailosaur.com/docs/api#list-all-messages)", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - mailosaur, - db: "$.service.db", - serverId: { - propDefinition: [ - mailosaur, - "serverId", - ], - }, - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: 60, - }, - }, - receiveAfter: { - propDefinition: [ - mailosaur, - "receiveAfter", - ], - }, - }, methods: { - _getLastTimestamp() { - return this.db.get("lastTimestamp"); - }, - _setLastTimestamp(timestamp) { - this.db.set("lastTimestamp", timestamp); - }, - }, - hooks: { - async deploy() { - const receiveAfter = this.receiveAfter || new Date(new Date().setDate(new Date().getDate() - 1)).toISOString(); - const response = await this.mailosaur.searchMessages({ - serverId: this.serverId, - receiveAfter, - itemsPerPage: 50, - }); - - const messages = response.items; - messages.reverse().slice(0, 50) - .forEach((message) => { - this.$emit(message, { - id: message.id, - summary: `New Message: ${message.subject}`, - ts: Date.parse(message.received), - }); - }); - - if (messages.length) { - this._setLastTimestamp(messages[messages.length - 1].received); - } + ...common.methods, + getFunction() { + return this.mailosaur.listMessages; }, - async activate() { - // Hook for activating the component + getSummary(item) { + return `New Message: ${item.subject}`; }, - async deactivate() { - // Hook for deactivating the component - }, - }, - async run() { - const lastTimestamp = this._getLastTimestamp() || new Date(new Date().setDate(new Date().getDate() - 1)).toISOString(); - - const response = await this.mailosaur.searchMessages({ - serverId: this.serverId, - receiveAfter: lastTimestamp, - itemsPerPage: 50, - }); - - const messages = response.items; - messages.reverse().forEach((message) => { - this.$emit(message, { - id: message.id, - summary: `New Message: ${message.subject}`, - ts: Date.parse(message.received), - }); - }); - - if (messages.length) { - this._setLastTimestamp(messages[messages.length - 1].received); - } }, + sampleEmit, }; diff --git a/components/mailosaur/sources/new-message/test-event.mjs b/components/mailosaur/sources/new-message/test-event.mjs new file mode 100644 index 0000000000000..2a4a1866c8b79 --- /dev/null +++ b/components/mailosaur/sources/new-message/test-event.mjs @@ -0,0 +1,16 @@ +export default { + "id": "77061c9f-da47-4009-9f33-9715a3bbf00c", + "received": "2019-08-06T17:44:07.197781+00:00", + "type": "Email", + "subject": "Email subject line", + "from": [{ + "name": "Acme", + "email": "noreply@example.com" + }], + "to": [{ + "name": "Jane Doe", + "email": "janedoe@abc1234.mailosaur.net" + }], + "cc": [], + "bcc": [] +} \ No newline at end of file From 7ddb44c7fa9731a71aebb884c3ed3c4452a336a8 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Thu, 15 May 2025 17:18:27 -0300 Subject: [PATCH 3/6] pnpm update --- pnpm-lock.yaml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ad5fb3e8eacb..90b987134abfc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2143,8 +2143,7 @@ importers: specifier: ^1.5.1 version: 1.6.6 - components/cerebras: - specifiers: {} + components/cerebras: {} components/certifier: dependencies: @@ -7759,12 +7758,14 @@ importers: components/mailninja: {} components/mailosaur: - specifiers: {} + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/mailrefine: {} - components/mailrelay: - specifiers: {} + components/mailrelay: {} components/mails_so: dependencies: @@ -10173,8 +10174,7 @@ importers: specifier: ^1.5.1 version: 1.6.6 - components/prepr_graphql: - specifiers: {} + components/prepr_graphql: {} components/prerender: dependencies: @@ -11544,8 +11544,7 @@ importers: components/sellsy: {} - components/selzy: - specifiers: {} + components/selzy: {} components/semaphore: {} @@ -35637,6 +35636,8 @@ snapshots: '@putout/operator-filesystem': 5.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3)) '@putout/operator-json': 2.2.0 putout: 36.13.1(eslint@8.57.1)(typescript@5.6.3) + transitivePeerDependencies: + - supports-color '@putout/operator-regexp@1.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3))': dependencies: From e5df4c0dfb343a2493b19a6091f1d6ad3422fdc1 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Thu, 15 May 2025 17:24:57 -0300 Subject: [PATCH 4/6] pnpm update --- pnpm-lock.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd4b7fa80242b..2092f4afe2cc2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11428,8 +11428,7 @@ importers: components/scrapein_: {} - components/scrapeless: - specifiers: {} + components/scrapeless: {} components/scrapeninja: dependencies: From 9cd2cb1fc323e65c8e0430fc82619e8d58482aae Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 16 May 2025 10:04:55 -0300 Subject: [PATCH 5/6] some adjusts --- .../mailosaur/actions/search-email/search-email.mjs | 11 ++++------- components/mailosaur/sources/common/base.mjs | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/components/mailosaur/actions/search-email/search-email.mjs b/components/mailosaur/actions/search-email/search-email.mjs index 962e2334c22e2..51efd8e100f1b 100644 --- a/components/mailosaur/actions/search-email/search-email.mjs +++ b/components/mailosaur/actions/search-email/search-email.mjs @@ -1,3 +1,4 @@ +import { MATCH_OPTIONS } from "../../common/constants.mjs"; import mailosaur from "../../mailosaur.app.mjs"; export default { @@ -55,24 +56,20 @@ export default { subject: { type: "string", label: "Subject", - description: "The value to seek within the target email’s subject line.", + description: "The value to seek within the target email's subject line.", optional: true, }, body: { type: "string", label: "Body", - description: "The value to seek within the target message’s HTML or text body.", + description: "The value to seek within the target message's HTML or text body.", optional: true, }, match: { type: "string", label: "Match", description: "If set to `ALL` (default), only results matching all criteria will be returned. If set to `ANY`, results matching any criteria will be returned.", - options: [ - "ALL", - "ANY", - ], - default: "ALL", + options: MATCH_OPTIONS, optional: true, }, }, diff --git a/components/mailosaur/sources/common/base.mjs b/components/mailosaur/sources/common/base.mjs index 49f1a4ef65efe..1c95503c2a499 100644 --- a/components/mailosaur/sources/common/base.mjs +++ b/components/mailosaur/sources/common/base.mjs @@ -33,8 +33,8 @@ export default { const response = this.mailosaur.paginate({ fn: this.getFunction(), - receivedAfter: lastDate, params: { + receivedAfter: lastDate, server: this.serverId, }, data: this.getData(), From f5f3ca1c4c60ec157a9af1644b80bdc9f0d4176a Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 19 May 2025 10:21:31 -0300 Subject: [PATCH 6/6] some adjusts --- components/mailosaur/sources/common/base.mjs | 10 ++++++++- .../new-email-matching-criteria.mjs | 22 +++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/components/mailosaur/sources/common/base.mjs b/components/mailosaur/sources/common/base.mjs index 1c95503c2a499..86977ce8825a2 100644 --- a/components/mailosaur/sources/common/base.mjs +++ b/components/mailosaur/sources/common/base.mjs @@ -28,16 +28,23 @@ export default { getData() { return {}; }, + dataValidation() { + return true; + }, + getOtherOpts() { + return {}; + }, async emitEvent(maxResults = false) { const lastDate = this._getLastDate(); + const otherOpts = this.getOtherOpts(); const response = this.mailosaur.paginate({ fn: this.getFunction(), params: { receivedAfter: lastDate, server: this.serverId, }, - data: this.getData(), + ...otherOpts, }); let responseArray = []; @@ -68,6 +75,7 @@ export default { }, }, async run() { + await this.dataValidation(); await this.emitEvent(); }, }; diff --git a/components/mailosaur/sources/new-email-matching-criteria/new-email-matching-criteria.mjs b/components/mailosaur/sources/new-email-matching-criteria/new-email-matching-criteria.mjs index 4efda8152e56b..9e5fac17b98cb 100644 --- a/components/mailosaur/sources/new-email-matching-criteria/new-email-matching-criteria.mjs +++ b/components/mailosaur/sources/new-email-matching-criteria/new-email-matching-criteria.mjs @@ -1,3 +1,4 @@ +import { ConfigurationError } from "@pipedream/platform"; import { MATCH_OPTIONS } from "../../common/constants.mjs"; import common from "../common/base.mjs"; import sampleEmit from "./test-event.mjs"; @@ -41,20 +42,27 @@ export default { label: "Match", description: "If set to `ALL` (default), then only results that match all specified criteria will be returned. If set to `ANY`, results that match any of the specified criteria will be returned.", options: MATCH_OPTIONS, - optional: true, }, }, methods: { ...common.methods, - getData() { + getOtherOpts() { return { - sentFrom: this.sentFrom, - sentTo: this.sentTo, - subject: this.subject, - body: this.body, - match: this.match, + data: { + sentFrom: this.sentFrom, + sentTo: this.sentTo, + subject: this.subject, + body: this.body, + match: this.match, + }, }; }, + dataValidation() { + if (!this.sentFrom && !this.sentTo && !this.subject && !this.body) { + throw new ConfigurationError("Please provide at least one search criteria."); + } + return true; + }, getFunction() { return this.mailosaur.searchMessages; },