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..b7b27a880dbcb --- /dev/null +++ b/components/mailosaur/actions/create-email/create-email.mjs @@ -0,0 +1,74 @@ +import { ConfigurationError } from "@pipedream/platform"; +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: { + type: "string", + label: "To", + description: "The verified external email address to which the email should be sent.", + }, + 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: { + 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: { + 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: { + type: "boolean", + label: "Send", + description: "If `false`, the email will be created in your server, but will not be sent.", + }, + }, + async run({ $ }) { + 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}`); + 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..8cf762ac3ba78 --- /dev/null +++ b/components/mailosaur/actions/delete-email/delete-email.mjs @@ -0,0 +1,37 @@ +import mailosaur from "../../mailosaur.app.mjs"; + +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, + serverId: { + propDefinition: [ + mailosaur, + "serverId", + ], + }, + emailId: { + propDefinition: [ + mailosaur, + "emailId", + ({ serverId }) => ({ + serverId, + }), + ], + }, + }, + async run({ $ }) { + await this.mailosaur.deleteEmail({ + $, + emailId: this.emailId, + }); + $.export("$summary", `Successfully deleted email with ID ${this.emailId}`); + return { + 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..51efd8e100f1b --- /dev/null +++ b/components/mailosaur/actions/search-email/search-email.mjs @@ -0,0 +1,98 @@ +import { MATCH_OPTIONS } from "../../common/constants.mjs"; +import mailosaur from "../../mailosaur.app.mjs"; + +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.1", + type: "action", + props: { + mailosaur, + serverId: { + propDefinition: [ + mailosaur, + "serverId", + ], + }, + 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, + }, + 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: MATCH_OPTIONS, + optional: true, + }, + }, + async run({ $ }) { + const response = await this.mailosaur.searchMessages({ + $, + 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.`); + return response; + }, +}; 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 5b7f545d2c022..68780a34084af 100644 --- a/components/mailosaur/mailosaur.app.mjs +++ b/components/mailosaur/mailosaur.app.mjs @@ -1,11 +1,123 @@ +import { axios } from "@pipedream/platform"; +import { LIMIT } from "./common/constants.mjs"; + export default { type: "app", app: "mailosaur", - propDefinitions: {}, + propDefinitions: { + serverId: { + type: "string", + label: "Server ID", + description: "The identifier of the server from which the email should be sent.", + async options() { + const { items } = await this.listServers(); + + return items.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + emailId: { + type: "string", + 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: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://mailosaur.com/api"; + }, + _auth() { + return { + username: "api", + password: `${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + auth: this._auth(), + ...opts, + }); + }, + listServers() { + return this._makeRequest({ + path: "/servers", + }); + }, + listMessages(opts = {}) { + return this._makeRequest({ + path: "/messages", + ...opts, + }); + }, + sendEmail(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/messages", + ...opts, + }); + }, + searchMessages(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/messages/search", + ...opts, + }); + }, + deleteEmail({ + emailId, ...opts + }) { + return this._makeRequest({ + method: "DELETE", + path: `/messages/${emailId}`, + ...opts, + }); + }, + async *paginate({ + fn, params = {}, maxResults = null, ...opts + }) { + let hasMore = false; + let count = 0; + let page = 0; + + do { + params.page = page++; + params.itemsPerPage = LIMIT; + const { items } = await fn({ + params, + ...opts, + }); + for (const d of items) { + yield d; + + if (maxResults && ++count === maxResults) { + return count; + } + } + + hasMore = items.length; + + } while (hasMore); }, }, -}; \ No newline at end of file +}; diff --git a/components/mailosaur/package.json b/components/mailosaur/package.json index e06b1c0211a94..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" } -} \ No newline at end of file +} diff --git a/components/mailosaur/sources/common/base.mjs b/components/mailosaur/sources/common/base.mjs new file mode 100644 index 0000000000000..86977ce8825a2 --- /dev/null +++ b/components/mailosaur/sources/common/base.mjs @@ -0,0 +1,81 @@ +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 {}; + }, + 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, + }, + ...otherOpts, + }); + + 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.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 new file mode 100644 index 0000000000000..9e5fac17b98cb --- /dev/null +++ b/components/mailosaur/sources/new-email-matching-criteria/new-email-matching-criteria.mjs @@ -0,0 +1,74 @@ +import { ConfigurationError } 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 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: { + ...common.props, + sentFrom: { + type: "string", + label: "Sent From", + 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 full email address or phone number 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), 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, + }, + }, + methods: { + ...common.methods, + getOtherOpts() { + return { + 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; + }, + getSummary(item) { + return `New Message: ${item.subject}`; + }, + }, + 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 new file mode 100644 index 0000000000000..2622687f8b9e8 --- /dev/null +++ b/components/mailosaur/sources/new-message/new-message.mjs @@ -0,0 +1,22 @@ +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#list-all-messages)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getFunction() { + return this.mailosaur.listMessages; + }, + getSummary(item) { + return `New Message: ${item.subject}`; + }, + }, + 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 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98ee964905365..659af0f0bcb58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7761,7 +7761,11 @@ importers: components/mailninja: {} - components/mailosaur: {} + components/mailosaur: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/mailrefine: {}