From 9d9f86eeff20139cfa87acba3b04ce155071669e Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 16 May 2025 10:02:36 -0300 Subject: [PATCH 01/10] selzy init --- .../create-campaign/create-campaign.mjs | 55 ++++++++ .../create-email-message.mjs | 77 +++++++++++ components/selzy/package.json | 2 +- components/selzy/selzy.app.mjs | 128 +++++++++++++++++- .../new-campaign-instant.mjs | 104 ++++++++++++++ .../new-campaign-status-instant.mjs | 117 ++++++++++++++++ .../new-subscriber-instant.mjs | 126 +++++++++++++++++ 7 files changed, 603 insertions(+), 6 deletions(-) create mode 100644 components/selzy/actions/create-campaign/create-campaign.mjs create mode 100644 components/selzy/actions/create-email-message/create-email-message.mjs create mode 100644 components/selzy/sources/new-campaign-instant/new-campaign-instant.mjs create mode 100644 components/selzy/sources/new-campaign-status-instant/new-campaign-status-instant.mjs create mode 100644 components/selzy/sources/new-subscriber-instant/new-subscriber-instant.mjs diff --git a/components/selzy/actions/create-campaign/create-campaign.mjs b/components/selzy/actions/create-campaign/create-campaign.mjs new file mode 100644 index 0000000000000..e93918d83879f --- /dev/null +++ b/components/selzy/actions/create-campaign/create-campaign.mjs @@ -0,0 +1,55 @@ +import selzy from "../../selzy.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "selzy-create-email-campaign", + name: "Create Email Campaign", + description: "Creates a new email campaign. [See the documentation](https://selzy.com/en/support/api/messages/createcampaign/)", + version: "0.0.{{ts}}", + type: "action", + props: { + selzy, + subject: { + propDefinition: [ + selzy, + "subject", + ], + }, + senderName: { + propDefinition: [ + selzy, + "senderName", + ], + }, + senderEmail: { + propDefinition: [ + selzy, + "senderEmail", + ], + }, + messageContent: { + propDefinition: [ + selzy, + "messageContent", + ], + }, + listId: { + propDefinition: [ + selzy, + "listId", + ], + }, + }, + async run({ $ }) { + const response = await this.selzy.createCampaign({ + subject: this.subject, + sender_name: this.senderName, + sender_email: this.senderEmail, + message_content: this.messageContent, + list_id: this.listId, + }); + + $.export("$summary", `Successfully created email campaign with ID: ${response.campaign_id}`); + return response; + }, +}; diff --git a/components/selzy/actions/create-email-message/create-email-message.mjs b/components/selzy/actions/create-email-message/create-email-message.mjs new file mode 100644 index 0000000000000..27f6af135c00c --- /dev/null +++ b/components/selzy/actions/create-email-message/create-email-message.mjs @@ -0,0 +1,77 @@ +import selzy from "../../selzy.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "selzy-create-email-message", + name: "Create Email Message", + description: "Adds a new email message. [See the documentation](https://selzy.com/en/support/category/api/messages/)", + version: "0.0.{{ts}}", + type: "action", + props: { + selzy, + senderName: { + propDefinition: [ + selzy, + "senderName", + ], + }, + senderEmail: { + propDefinition: [ + selzy, + "senderEmail", + ], + }, + subject: { + propDefinition: [ + selzy, + "subject", + ], + }, + body: { + propDefinition: [ + selzy, + "body", + ], + }, + listId: { + propDefinition: [ + selzy, + "listId", + ], + }, + name: { + type: "string", + label: "Name", + description: "The optional name for the email message", + optional: true, + }, + customFields: { + type: "object", + label: "Custom Fields", + description: "Optional custom fields for the email message", + optional: true, + }, + }, + async run({ $ }) { + const emailMessageData = { + sender_name: this.senderName, + sender_email: this.senderEmail, + subject: this.subject, + body: this.body, + list_id: this.listId, + }; + + if (this.name) { + emailMessageData.name = this.name; + } + + if (this.customFields) { + Object.assign(emailMessageData, this.customFields); + } + + const response = await this.selzy.createEmailMessage(emailMessageData); + + $.export("$summary", `Email message created successfully with ID ${response.message_id}.`); + return response; + }, +}; diff --git a/components/selzy/package.json b/components/selzy/package.json index 656621febf866..13f6dde2d71f1 100644 --- a/components/selzy/package.json +++ b/components/selzy/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/selzy/selzy.app.mjs b/components/selzy/selzy.app.mjs index d4c9ac28f68ce..a8d92fd94d98a 100644 --- a/components/selzy/selzy.app.mjs +++ b/components/selzy/selzy.app.mjs @@ -1,11 +1,129 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "selzy", - propDefinitions: {}, + propDefinitions: { + listId: { + type: "string", + label: "List ID", + description: "Select or enter the List ID to monitor or target", + async options() { + const lists = await this.getLists(); + return lists.map((list) => ({ + label: list.name, + value: list.id, + })); + }, + }, + campaignId: { + type: "string", + label: "Campaign ID", + description: "Select or enter the Campaign ID to monitor", + async options() { + const campaigns = await this.getCampaigns(); + return campaigns.map((campaign) => ({ + label: campaign.name, + value: campaign.id, + })); + }, + }, + senderEmail: { + type: "string", + label: "Sender Email", + description: "The sender's email address", + }, + senderName: { + type: "string", + label: "Sender Name", + description: "The sender's name", + }, + subject: { + type: "string", + label: "Subject", + description: "The subject of the email", + }, + body: { + type: "string", + label: "Body", + description: "The HTML body of the email", + }, + messageContent: { + type: "string", + label: "Message Content", + description: "Content of the email message", + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.selzy.com/en/api"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, + method = "GET", + path = "/", + headers, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method, + url: `${this._baseUrl()}${path}`, + headers: { + ...headers, + api_key: this.$auth.api_key, + }, + }); + }, + async getLists(opts = {}) { + return this._makeRequest({ + path: "/lists", + ...opts, + }); + }, + async getCampaigns(opts = {}) { + return this._makeRequest({ + path: "/campaigns", + ...opts, + }); + }, + async createEmailMessage(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/createEmailMessage", + data: { + sender_name: this.senderName, + sender_email: this.senderEmail, + subject: this.subject, + body: this.body, + list_id: this.listId, + ...opts, + }, + }); + }, + async createCampaign(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/createCampaign", + data: { + subject: this.subject, + sender_name: this.senderName, + sender_email: this.senderEmail, + message_content: this.messageContent, + list_id: this.listId, + ...opts, + }, + }); + }, + async subscribeToNewContact(listId) { + // Logic for webhook or polling to emit new event on new contact subscription + }, + async subscribeToNewCampaign() { + // Logic for webhook or polling to emit new event when a new campaign is created + }, + async subscribeToCampaignStatusChange(campaignId) { + // Logic for webhook or polling to emit new event on campaign status change }, }, -}; \ No newline at end of file +}; diff --git a/components/selzy/sources/new-campaign-instant/new-campaign-instant.mjs b/components/selzy/sources/new-campaign-instant/new-campaign-instant.mjs new file mode 100644 index 0000000000000..382fc509c0ddb --- /dev/null +++ b/components/selzy/sources/new-campaign-instant/new-campaign-instant.mjs @@ -0,0 +1,104 @@ +import selzy from "../../selzy.app.mjs"; +import { axios } from "@pipedream/platform"; +import crypto from "crypto"; + +export default { + key: "selzy-new-campaign-instant", + name: "New Campaign Created", + description: "Emit new event when a new email campaign is created. Useful for monitoring campaign creation activity. [See the documentation](https://selzy.com/en/support/category/api/)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + selzy, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + }, + methods: { + _getWebhookId() { + return this.db.get("webhookId"); + }, + _setWebhookId(id) { + this.db.set("webhookId", id); + }, + }, + hooks: { + async deploy() { + const campaigns = await this.selzy.getCampaigns({ + paginate: true, + max: 50, + }); + for (const campaign of campaigns) { + this.$emit(campaign, { + id: campaign.id, + summary: `New campaign created: ${campaign.name}`, + ts: Date.parse(campaign.createdAt), + }); + } + }, + async activate() { + const response = await this.selzy._makeRequest({ + method: "POST", + path: "/setHook", + data: { + api_key: this.selzy.$auth.api_key, + hook_url: this.http.endpoint, + event_format: "json_post", + events: [ + "campaign_status", + ], + status: "active", + }, + }); + this._setWebhookId(response.hook_id); + }, + async deactivate() { + const webhookId = this._getWebhookId(); + if (webhookId) { + await this.selzy._makeRequest({ + method: "POST", + path: "/removeHook", + data: { + hook_id: webhookId, + }, + }); + } + }, + }, + async run(event) { + const signature = event.headers["x-selzy-signature"]; + const secret = this.selzy.$auth.api_key; + const computedSignature = crypto.createHmac("sha256", secret) + .update(event.body) + .digest("base64"); + + if (computedSignature !== signature) { + this.http.respond({ + status: 401, + body: "Unauthorized", + }); + return; + } + + const { events_by_user } = event.body; + for (const userEvents of events_by_user) { + for (const eventData of userEvents.Events) { + if (eventData.event_name === "campaign_status") { + this.$emit(eventData, { + id: eventData.event_data.campaign_id, + summary: `New campaign status: ${eventData.event_data.status}`, + ts: Date.parse(eventData.event_time), + }); + } + } + } + + this.http.respond({ + status: 200, + body: "OK", + }); + }, +}; diff --git a/components/selzy/sources/new-campaign-status-instant/new-campaign-status-instant.mjs b/components/selzy/sources/new-campaign-status-instant/new-campaign-status-instant.mjs new file mode 100644 index 0000000000000..cc4eff0ea5f4f --- /dev/null +++ b/components/selzy/sources/new-campaign-status-instant/new-campaign-status-instant.mjs @@ -0,0 +1,117 @@ +import selzy from "../../selzy.app.mjs"; +import crypto from "crypto"; +import { axios } from "@pipedream/platform"; + +export default { + key: "selzy-new-campaign-status-instant", + name: "New Campaign Status Instant", + description: "Emit a new event when the status of a campaign changes. [See the documentation](https://selzy.com/en/support/api/common/event-notification-system-webhooks/)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + selzy, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + campaignId: { + propDefinition: [ + selzy, + "campaignId", + ], + }, + }, + methods: { + _getWebhookId() { + return this.db.get("webhookId"); + }, + _setWebhookId(id) { + this.db.set("webhookId", id); + }, + _generateAuth(authBody) { + return crypto.createHash("md5").update(authBody) + .digest("hex"); + }, + }, + hooks: { + async deploy() { + const recentCampaigns = await this.selzy.getCampaigns({ + paginate: true, + max: 50, + }); + for (const campaign of recentCampaigns) { + this.$emit(campaign, { + id: campaign.id, + summary: `New campaign status: ${campaign.status}`, + ts: new Date(campaign.modified).getTime(), + }); + } + }, + async activate() { + const hookUrl = this.http.endpoint; + const hookId = await this.selzy._makeRequest({ + method: "POST", + path: "/setHook", + data: { + api_key: this.selzy.$auth.api_key, + hook_url: hookUrl, + event_format: "json_post", + events: { + campaign_status: this.campaignId + ? [ + this.campaignId, + ] + : [ + "*", + ], + }, + single_event: 0, + status: "active", + }, + }); + this._setWebhookId(hookId); + }, + async deactivate() { + const hookId = this._getWebhookId(); + await this.selzy._makeRequest({ + method: "POST", + path: "/deleteHook", + data: { + api_key: this.selzy.$auth.api_key, + id: hookId, + }, + }); + }, + }, + async run(event) { + const signature = event.headers["selzy-signature"]; + const computedSignature = this._generateAuth(JSON.stringify(event.body).replace(event.body.auth, this.selzy.$auth.api_key)); + + if (signature !== computedSignature) { + this.http.respond({ + status: 401, + body: "Unauthorized", + }); + return; + } + + this.http.respond({ + status: 200, + }); + + const { events_by_user } = event.body; + for (const userEvents of events_by_user) { + for (const eventData of userEvents.Events) { + if (eventData.event_name === "campaign_status") { + this.$emit(eventData.event_data, { + id: `${eventData.event_time}-${eventData.event_data.campaign_id}`, + summary: `Campaign status updated to ${eventData.event_data.status}`, + ts: new Date(eventData.event_time).getTime(), + }); + } + } + } + }, +}; diff --git a/components/selzy/sources/new-subscriber-instant/new-subscriber-instant.mjs b/components/selzy/sources/new-subscriber-instant/new-subscriber-instant.mjs new file mode 100644 index 0000000000000..a8e2988f2470a --- /dev/null +++ b/components/selzy/sources/new-subscriber-instant/new-subscriber-instant.mjs @@ -0,0 +1,126 @@ +import selzy from "../../selzy.app.mjs"; +import crypto from "crypto"; +import { axios } from "@pipedream/platform"; + +export default { + key: "selzy-new-subscriber-instant", + name: "New Subscriber Instant", + description: "Emit a new event when a new contact subscribes to a specified list. [See the documentation](https://selzy.com/en/support/api/common/event-notification-system-webhooks/)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + selzy, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + listId: { + propDefinition: [ + selzy, + "listId", + ], + }, + }, + methods: { + _getWebhookId() { + return this.db.get("webhookId"); + }, + _setWebhookId(id) { + this.db.set("webhookId", id); + }, + _generateAuth(body, apiKey) { + const modifiedBody = { + ...body, + auth: apiKey, + }; + return crypto.createHash("md5").update(JSON.stringify(modifiedBody)) + .digest("hex"); + }, + _verifySignature(body, expectedAuth) { + const actualAuth = this._generateAuth(body, this.selzy.$auth.api_key); + return expectedAuth === actualAuth; + }, + async _createWebhook() { + const response = await this.selzy._makeRequest({ + method: "POST", + path: "/setHook", + data: { + api_key: this.selzy.$auth.api_key, + hook_url: this.http.endpoint, + events: { + "subscribe": this.listId, + }, + event_format: "json_post", + single_event: 1, + status: "active", + }, + }); + this._setWebhookId(response.hook_id); + }, + async _removeWebhook() { + const webhookId = this._getWebhookId(); + if (webhookId) { + await this.selzy._makeRequest({ + method: "POST", + path: "/removeHook", + data: { + api_key: this.selzy.$auth.api_key, + hook_id: webhookId, + }, + }); + } + }, + }, + hooks: { + async deploy() { + const subscriptions = await this.selzy._makeRequest({ + path: "/subscriptions", + method: "GET", + }); + const recent = subscriptions.slice(-50); + for (const subscription of recent) { + this.$emit(subscription, { + id: subscription.email_id, + summary: `New subscriber: ${subscription.email}`, + ts: Date.parse(subscription.created_at), + }); + } + }, + async activate() { + await this._createWebhook(); + }, + async deactivate() { + await this._removeWebhook(); + }, + }, + async run(event) { + const { + auth, events_by_user, + } = event.body; + const isValid = this._verifySignature(event.body, auth); + if (!isValid) { + this.http.respond({ + status: 401, + body: "Unauthorized", + }); + return; + } + for (const userEvents of events_by_user) { + for (const eventInfo of userEvents.Events) { + if (eventInfo.event_name === "subscribe" && eventInfo.event_data.just_subscribed_list_ids.includes(this.listId)) { + this.$emit(eventInfo.event_data, { + id: eventInfo.event_data.email, + summary: `New subscriber: ${eventInfo.event_data.email}`, + ts: Date.parse(eventInfo.event_time), + }); + } + } + } + this.http.respond({ + status: 200, + body: "OK", + }); + }, +}; From 5bfb2d46e6eb00262a584cd55e3e8bba82b0c99f Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 19 May 2025 10:00:59 -0300 Subject: [PATCH 02/10] [Components] selzy #16657 Sources - New Campaign - New Campaign Status (Instant) - New Subscriber (Instant) Actions - Create Subscriber - Send Campaign - Create Campaign --- .../create-campaign/create-campaign.mjs | 152 +++++++++---- .../create-email-message.mjs | 134 ++++++++---- components/selzy/common/constants.mjs | 35 +++ components/selzy/common/utils.mjs | 38 ++++ components/selzy/package.json | 5 +- components/selzy/selzy.app.mjs | 205 ++++++++++++------ components/selzy/sources/common/base.mjs | 49 +++++ .../new-campaign-instant.mjs | 104 --------- .../new-campaign-status-instant.mjs | 121 ++--------- .../test-event.mjs | 13 ++ .../sources/new-campaign/new-campaign.mjs | 78 +++++++ .../selzy/sources/new-campaign/test-event.mjs | 11 + .../new-subscriber-instant.mjs | 127 ++--------- .../new-subscriber-instant/test-event.mjs | 10 + 14 files changed, 620 insertions(+), 462 deletions(-) create mode 100644 components/selzy/common/constants.mjs create mode 100644 components/selzy/common/utils.mjs create mode 100644 components/selzy/sources/common/base.mjs delete mode 100644 components/selzy/sources/new-campaign-instant/new-campaign-instant.mjs create mode 100644 components/selzy/sources/new-campaign-status-instant/test-event.mjs create mode 100644 components/selzy/sources/new-campaign/new-campaign.mjs create mode 100644 components/selzy/sources/new-campaign/test-event.mjs create mode 100644 components/selzy/sources/new-subscriber-instant/test-event.mjs diff --git a/components/selzy/actions/create-campaign/create-campaign.mjs b/components/selzy/actions/create-campaign/create-campaign.mjs index e93918d83879f..29e088fa66edc 100644 --- a/components/selzy/actions/create-campaign/create-campaign.mjs +++ b/components/selzy/actions/create-campaign/create-campaign.mjs @@ -1,55 +1,133 @@ +import { ConfigurationError } from "@pipedream/platform"; +import { + clearEmpty, + parseObject, +} from "../../common/utils.mjs"; import selzy from "../../selzy.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "selzy-create-email-campaign", name: "Create Email Campaign", description: "Creates a new email campaign. [See the documentation](https://selzy.com/en/support/api/messages/createcampaign/)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", props: { selzy, - subject: { - propDefinition: [ - selzy, - "subject", - ], - }, - senderName: { - propDefinition: [ - selzy, - "senderName", - ], - }, - senderEmail: { - propDefinition: [ - selzy, - "senderEmail", - ], - }, - messageContent: { - propDefinition: [ - selzy, - "messageContent", - ], - }, - listId: { - propDefinition: [ - selzy, - "listId", - ], + messageId: { + type: "string", + label: "Message ID", + description: "Code of the message to be sent. The code returned by the **Create Email Message** method should be transferred.", + optional: true, + }, + startTime: { + type: "string", + label: "Start Time", + description: "Campaign launch date and time in the \"YYYY-MM-DD hh:mm\" format, which do not exceed 100 days from the current date. If the argument is not set, the campaign starts immediately. The time zone specified in the settings of the user's personal account is applied. To explicitly specify a time zone, use the **Timezone** argument. To provide additional error protection, you should not schedule two sendings of the same message within an hour.", + optional: true, + }, + trackRead: { + type: "boolean", + label: "Track Read", + description: "Whether to track the fact of reading the email message. The default value is `false` (do not track). If `true`, a link to a small image tracking the reference will be added to the email. The **Track Read** argument is ignored for SMS messages.", + optional: true, + }, + trackLinks: { + type: "boolean", + label: "Track Links", + description: "To track whether there are any click-throughs in email messages, the default value is `false` (do not track). If `true`, all external links will be replaced with special ones that allow you to track the fact of a click-through, and then forward the user to the desired page. The **Track Links** argument is ignored for SMS messages.", + optional: true, + }, + contacts: { + type: "string[]", + label: "Contacts", + description: "Email addresses (or phone numbers for sms messages) to which sending of a message should be limited. If this argument is absent, sending will be made to all contacts on the list for which the message is made (possibly, taking into account segmentation by tags). If the contacts argument is present, only those contacts that are on the list will be taken into account, while the other will be ignored. If there are too many addresses (phone numbers) for sending in the contacts parameter, the **Contacts URL** parameter can be used instead. You can't set both parameters at the same time", + optional: true, + }, + contactsUrl: { + type: "string", + label: "Contacts URL", + description: "Instead of the contacts parameter containing the actual email addresses or phone numbers, in this parameter you can specify the URL of the file from which the addresses (phone numbers) will be read. The URL must start with \"http://\", \"https://\" or \"ftp://\". The file must contain one contact per string, without commas; strings must be separated by \"n\" or \"rn\" (Mac format — only \"r\" — not supported). The file can be deleted after the campaign has shifted to the 'scheduled' status.", + optional: true, + }, + trackGa: { + type: "boolean", + label: "Track GA", + description: "Whether to enable Google Analytics integration for this campaign. Only explicitly indicated values are valid, default usage parameters are not applied. The default value is `false` (disabled).", + optional: true, + reloadProps: true, + }, + gaMedium: { + type: "string", + label: "GA Medium", + description: "Integration parameters with Google Analytics (valid if track_ga=1). Only explicitly indicated values are valid, default usage parameters are not applied.", + optional: true, + hidden: true, + }, + gaSource: { + type: "string", + label: "GA Source", + description: "Integration parameters with Google Analytics (valid if track_ga=1). Only explicitly indicated values are valid, default usage parameters are not applied.", + optional: true, + hidden: true, + }, + gaCampaign: { + type: "string", + label: "GA Campaign", + description: "Integration parameters with Google Analytics (valid if track_ga=1). Only explicitly indicated values are valid, default usage parameters are not applied.", + optional: true, + hidden: true, + }, + gaContent: { + type: "string", + label: "GA Content", + description: "Integration parameters with Google Analytics (valid if track_ga=1). Only explicitly indicated values are valid, default usage parameters are not applied.", + optional: true, + hidden: true, + }, + gaTerm: { + type: "string", + label: "GA Term", + description: "Integration parameters with Google Analytics (valid if track_ga=1). Only explicitly indicated values are valid, default usage parameters are not applied.", + optional: true, + hidden: true, }, }, + async additionalProps(props) { + const gaAllowed = this.trackGa; + props.gaMedium.hidden = !gaAllowed; + props.gaSource.hidden = !gaAllowed; + props.gaCampaign.hidden = !gaAllowed; + props.gaContent.hidden = !gaAllowed; + props.gaTerm.hidden = !gaAllowed; + + return {}; + }, async run({ $ }) { + if (this.contacts && this.contactsUrl) { + throw new ConfigurationError("You can't set both contacts and contactsUrl parameters at the same time"); + } + const response = await this.selzy.createCampaign({ - subject: this.subject, - sender_name: this.senderName, - sender_email: this.senderEmail, - message_content: this.messageContent, - list_id: this.listId, + $, + params: clearEmpty({ + message_id: this.messageId, + start_time: this.startTime, + track_read: this.trackRead && +this.trackRead, + track_links: this.trackLinks && +this.trackLinks, + contacts: parseObject(this.contacts)?.join(","), + contacts_url: this.contactsUrl, + track_ga: this.trackGa && +this.trackGa, + ga_medium: this.gaMedium, + ga_source: this.gaSource, + ga_campaign: this.gaCampaign, + ga_content: this.gaContent, + ga_term: this.gaTerm, + }), }); - $.export("$summary", `Successfully created email campaign with ID: ${response.campaign_id}`); + if (response.error) throw new ConfigurationError(response.error); + + $.export("$summary", `Successfully created email campaign with ID: ${response.result.campaign_id}`); return response; }, }; diff --git a/components/selzy/actions/create-email-message/create-email-message.mjs b/components/selzy/actions/create-email-message/create-email-message.mjs index 27f6af135c00c..e0d92fdc571c6 100644 --- a/components/selzy/actions/create-email-message/create-email-message.mjs +++ b/components/selzy/actions/create-email-message/create-email-message.mjs @@ -1,77 +1,137 @@ +import { ConfigurationError } from "@pipedream/platform"; +import { + MESSAGE_FORMAT_OPTIONS, WRAP_TYPE_OPTIONS, +} from "../../common/constants.mjs"; +import { parseObject } from "../../common/utils.mjs"; import selzy from "../../selzy.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "selzy-create-email-message", name: "Create Email Message", description: "Adds a new email message. [See the documentation](https://selzy.com/en/support/category/api/messages/)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", props: { selzy, senderName: { - propDefinition: [ - selzy, - "senderName", - ], + type: "string", + label: "Sender's name", + description: "It is a string that does not match the email address (the sender_email argument).", }, senderEmail: { - propDefinition: [ - selzy, - "senderEmail", - ], + type: "string", + label: "Sender's email address", + description: "This email must be checked (to do this, you need to manually create at least one email with this return address via the web interface, then click on the \"send the confirmation request\" link and follow the link from the email).", }, subject: { + type: "string", + label: "Subject", + description: "String with the letter subject. It may include [substitution fields](https://selzy.com/en/support/letter/other-functions/personalization-tags/). The parameter is optional if **Template Id** is indicated.", + optional: true, + }, + body: { + type: "string", + label: "Body", + description: "If you transfer the entire HTML text, test such letters additionally as headers outside the body may be modified. The parameter is optional if **Template Id** or **System Template Id** is indicated.", + }, + listId: { propDefinition: [ selzy, - "subject", + "listId", ], }, - body: { + textBody: { + type: "string", + label: "Text Body", + description: "Text version of the template. It is absent by default. If you do not provide the text version along with the HTML version, you are recommended to set the **Generate Text** parameter to 1 for automatic generation of the text part of the letter.", + optional: true, + }, + generateText: { + type: "boolean", + label: "Generate Text", + description: "`True` means that the text part of the letter will be generated automatically based on the HTML part. If you do not provide the text version along with the HTML version, you are recommended to set the **Generate Text** parameter to `true` for automatic generation of the text part of the letter. If the text variant of the letter is provided using the **Text Body** parameter, the **Generate Text** parameter is ignored. Thus, if the **Generate Text** value has been set to `true`, the server's response will contain a warning.", + optional: true, + }, + rawBody: { + type: "string", + label: "Raw Body", + description: "It is intended to save the json structure of the block editor data structure (if the value is **Message Format** = block) The parameter obtains only the JSON structure, otherwise it will not be transferred.", + optional: true, + }, + messageFormat: { + type: "string", + label: "Message Format", + description: `It defines the manner of creating a letter. + \n 1 - If you transfer the \`text\` value in this parameter and both the body and **Text Body** parameters are filled, the body parameter will be ignored, and the letter will be created from the data, transferred in the **Text Body** parameter. + \n 2 - If you transfer the \`block\` value in this parameter but do not specify **Raw Body**, the letter will be saved as **Raw HTML**. + \n 3 - If you transfer the \`block\` value in this parameter, the **body** and **Raw Body** parameters must be transferred so taht you can save the message in the block editor format.`, + options: MESSAGE_FORMAT_OPTIONS, + optional: true, + }, + lang: { + type: "string", + label: "Lang", + description: `Two-letter language code for the string with the unsubscribe link that is added to each letter automatically. + If it is not specified, the language code from the API URL is used. + In addition to the string with the unsubscribe link, this language also affects the interface of the unsubscribe page. Languages en, it, ua and ru are fully supported, and in case of some other languages (da, de, es, fr, nl, pl, pt, tr), the string with a link will be translated, and the control interface will be in English.`, + optional: true, + }, + templateId: { propDefinition: [ selzy, - "body", + "templateId", ], + optional: true, }, - listId: { + systemTemplateId: { propDefinition: [ selzy, - "listId", + "systemTemplateId", ], + optional: true, }, - name: { + wrapType: { type: "string", - label: "Name", - description: "The optional name for the email message", + label: "Wrap Type", + description: "Alignment of the message text on the specified side. If the argument is missing, the text will not be aligned.", + options: WRAP_TYPE_OPTIONS, optional: true, }, - customFields: { - type: "object", - label: "Custom Fields", - description: "Optional custom fields for the email message", + categories: { + type: "string[]", + label: "Categories", + description: "A list of letter categories.", optional: true, }, }, async run({ $ }) { - const emailMessageData = { - sender_name: this.senderName, - sender_email: this.senderEmail, - subject: this.subject, - body: this.body, - list_id: this.listId, - }; - - if (this.name) { - emailMessageData.name = this.name; + if (this.templateId && this.systemTemplateId) { + throw new ConfigurationError("You can only use one of the Template Id or System Template Id parameters."); } + const response = await this.selzy.createEmailMessage({ + $, + params: { + sender_name: this.senderName, + sender_email: this.senderEmail, + subject: this.subject, + body: this.body, + list_id: this.listId, - if (this.customFields) { - Object.assign(emailMessageData, this.customFields); - } + text_body: this.textBody, + generate_text: +this.generateText, + raw_body: this.rawBody, + message_format: this.messageFormat, + lang: this.lang, + template_id: this.templateId, + system_template_id: this.systemTemplateId, + wrap_type: this.wrapType, + categories: parseObject(this.categories)?.join(","), + }, + }); - const response = await this.selzy.createEmailMessage(emailMessageData); + if (response.error) throw new ConfigurationError(response.error); - $.export("$summary", `Email message created successfully with ID ${response.message_id}.`); + $.export("$summary", `Email message created successfully with ID ${response.result.message_id}.`); return response; }, }; diff --git a/components/selzy/common/constants.mjs b/components/selzy/common/constants.mjs new file mode 100644 index 0000000000000..d328f8e3fb11e --- /dev/null +++ b/components/selzy/common/constants.mjs @@ -0,0 +1,35 @@ +export const LIMIT = 100; + +export const MESSAGE_FORMAT_OPTIONS = [ + { + label: "Raw HTML", + value: "raw_html", + }, + { + label: "Block", + value: "block", + }, + { + label: "Text", + value: "text", + }, +]; + +export const WRAP_TYPE_OPTIONS = [ + { + label: "Skip (Do not apply)", + value: "skip", + }, + { + label: "Right (Right alignment)", + value: "right", + }, + { + label: "Left (Left alignment)", + value: "left", + }, + { + label: "Center (Center alignment)", + value: "center", + }, +]; diff --git a/components/selzy/common/utils.mjs b/components/selzy/common/utils.mjs new file mode 100644 index 0000000000000..fd39084463e37 --- /dev/null +++ b/components/selzy/common/utils.mjs @@ -0,0 +1,38 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; + +export const clearEmpty = (obj) => { + if (!obj) return undefined; + + const newObj = { + ...obj, + }; + Object.keys(newObj).forEach((key) => { + if (newObj[key] === "" || newObj[key] === null || newObj[key] === undefined) { + delete newObj[key]; + } + }); + return newObj; +}; diff --git a/components/selzy/package.json b/components/selzy/package.json index 13f6dde2d71f1..415ed88fed060 100644 --- a/components/selzy/package.json +++ b/components/selzy/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/selzy", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Selzy Components", "main": "selzy.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/selzy/selzy.app.mjs b/components/selzy/selzy.app.mjs index a8d92fd94d98a..f8a29b178cef1 100644 --- a/components/selzy/selzy.app.mjs +++ b/components/selzy/selzy.app.mjs @@ -1,4 +1,5 @@ import { axios } from "@pipedream/platform"; +import { LIMIT } from "./common/constants.mjs"; export default { type: "app", @@ -7,15 +8,82 @@ export default { listId: { type: "string", label: "List ID", - description: "Select or enter the List ID to monitor or target", + description: "Code of the list on which the mailing will be sent.", async options() { - const lists = await this.getLists(); - return lists.map((list) => ({ - label: list.name, - value: list.id, + const { result } = await this.listLists(); + + return result.map(({ + id: value, title: label, + }) => ({ + label, + value, + })); + }, + }, + templateId: { + type: "string", + label: "Template Id", + description: "ID of the user letter template created before, on the basis of which a letter can be created. If you have transferred this parameter, you may skip the mandatory **Subject**, **Body**, as well as **Text Body** and **Lang** parameters. These values will be taken from the corresponding parameters of the template the id of which was specified. If any of the above parameters is still transferred, the system will ignore the parameter that is taken from the template parameters, and the parameter explicitly transferred in this method will be used.", + async options({ page }) { + const { result } = await this.listTemplates({ + params: { + limit: LIMIT, + offset: LIMIT * page, + type: "user", + }, + }); + + return result.map(({ + id: value, title: label, + }) => ({ + label, + value, + })); + }, + }, + systemTemplateId: { + type: "string", + label: "System Template Id", + description: "ID of the system letter template created before, on the basis of which a letter can be created. If you have transferred this parameter, you may skip the mandatory **Subject**, **Body**, as well as **Text Body** and **Lang** parameters. These values will be taken from the corresponding parameters of the template the id of which was specified. If any of the above parameters is still transferred, the system will ignore the parameter that is taken from the template parameters, and the parameter explicitly transferred in this method will be used. If none of the **Template Id** or **System Template Id** parameters is specified, templates will not be used to create the letter.", + async options({ page }) { + const { result } = await this.listTemplates({ + params: { + limit: LIMIT, + offset: LIMIT * page, + type: "system", + }, + }); + + return result.map(({ + id: value, title: label, + }) => ({ + label, + value, + })); + }, + }, + messageId: { + type: "string", + label: "Message Id", + description: "Code of the message to be sent.", + async options({ page }) { + const { result } = await this.listTemplates({ + params: { + limit: LIMIT, + offset: LIMIT * page, + type: "system", + }, + }); + + return result.map(({ + id: value, title: label, + }) => ({ + label, + value, })); }, }, + campaignId: { type: "string", label: "Campaign ID", @@ -28,26 +96,6 @@ export default { })); }, }, - senderEmail: { - type: "string", - label: "Sender Email", - description: "The sender's email address", - }, - senderName: { - type: "string", - label: "Sender Name", - description: "The sender's name", - }, - subject: { - type: "string", - label: "Subject", - description: "The subject of the email", - }, - body: { - type: "string", - label: "Body", - description: "The HTML body of the email", - }, messageContent: { type: "string", label: "Message Content", @@ -58,72 +106,93 @@ export default { _baseUrl() { return "https://api.selzy.com/en/api"; }, - async _makeRequest(opts = {}) { - const { - $ = this, - method = "GET", - path = "/", - headers, - ...otherOpts - } = opts; + _params(params = {}) { + return { + api_key: `${this.$auth.api_key}`, + format: "json", + ...params, + }; + }, + _makeRequest({ + $ = this, path, params, ...opts + }) { return axios($, { - ...otherOpts, - method, - url: `${this._baseUrl()}${path}`, - headers: { - ...headers, - api_key: this.$auth.api_key, - }, + url: this._baseUrl() + path, + params: this._params(params), + ...opts, + }); + }, + listLists(opts = {}) { + return this._makeRequest({ + path: "/getLists", + ...opts, }); }, - async getLists(opts = {}) { + listTemplates(opts = {}) { return this._makeRequest({ - path: "/lists", + path: "/listTemplates", ...opts, }); }, - async getCampaigns(opts = {}) { + getCampaigns(opts = {}) { return this._makeRequest({ - path: "/campaigns", + path: "/getCampaigns", ...opts, }); }, - async createEmailMessage(opts = {}) { + createEmailMessage(opts = {}) { return this._makeRequest({ method: "POST", path: "/createEmailMessage", - data: { - sender_name: this.senderName, - sender_email: this.senderEmail, - subject: this.subject, - body: this.body, - list_id: this.listId, - ...opts, - }, + ...opts, }); }, - async createCampaign(opts = {}) { + createCampaign(opts = {}) { return this._makeRequest({ method: "POST", path: "/createCampaign", - data: { - subject: this.subject, - sender_name: this.senderName, - sender_email: this.senderEmail, - message_content: this.messageContent, - list_id: this.listId, - ...opts, - }, + ...opts, }); }, - async subscribeToNewContact(listId) { - // Logic for webhook or polling to emit new event on new contact subscription + createWebhook(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/setHook", + ...opts, + }); }, - async subscribeToNewCampaign() { - // Logic for webhook or polling to emit new event when a new campaign is created + deleteWebhook(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/removeHook", + ...opts, + }); }, - async subscribeToCampaignStatusChange(campaignId) { - // Logic for webhook or polling to emit new event on campaign status change + async *paginate({ + fn, params = {}, maxResults = null, ...opts + }) { + let hasMore = false; + let count = 0; + let page = 0; + + do { + params.limit = LIMIT; + params.offset = LIMIT * page++; + const { result } = await fn({ + params, + ...opts, + }); + for (const d of result) { + yield d; + + if (maxResults && ++count === maxResults) { + return count; + } + } + + hasMore = result.length; + + } while (hasMore); }, }, }; diff --git a/components/selzy/sources/common/base.mjs b/components/selzy/sources/common/base.mjs new file mode 100644 index 0000000000000..78e1f0770cd6a --- /dev/null +++ b/components/selzy/sources/common/base.mjs @@ -0,0 +1,49 @@ +import selzy from "../../selzy.app.mjs"; + +export default { + props: { + selzy, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + }, + hooks: { + async activate() { + await this.selzy.createWebhook({ + params: { + hook_url: this.http.endpoint, + event_format: "json_post", + ...this.getEventType(), + single_event: 1, + status: "active", + }, + }); + }, + async deactivate() { + await this.selzy.deleteWebhook({ + params: { + hook_url: this.http.endpoint, + }, + }); + }, + }, + async run({ + body, method, + }) { + if (method === "GET") { + this.http.respond({ + status: 200, + }); + return true; + } + + const ts = Date.parse(body.event_time); + this.$emit(body, { + id: `${body.campaign_id || body.email}-${ts}`, + summary: this.getSummary(body), + ts: ts, + }); + }, +}; diff --git a/components/selzy/sources/new-campaign-instant/new-campaign-instant.mjs b/components/selzy/sources/new-campaign-instant/new-campaign-instant.mjs deleted file mode 100644 index 382fc509c0ddb..0000000000000 --- a/components/selzy/sources/new-campaign-instant/new-campaign-instant.mjs +++ /dev/null @@ -1,104 +0,0 @@ -import selzy from "../../selzy.app.mjs"; -import { axios } from "@pipedream/platform"; -import crypto from "crypto"; - -export default { - key: "selzy-new-campaign-instant", - name: "New Campaign Created", - description: "Emit new event when a new email campaign is created. Useful for monitoring campaign creation activity. [See the documentation](https://selzy.com/en/support/category/api/)", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - selzy, - http: { - type: "$.interface.http", - customResponse: true, - }, - db: "$.service.db", - }, - methods: { - _getWebhookId() { - return this.db.get("webhookId"); - }, - _setWebhookId(id) { - this.db.set("webhookId", id); - }, - }, - hooks: { - async deploy() { - const campaigns = await this.selzy.getCampaigns({ - paginate: true, - max: 50, - }); - for (const campaign of campaigns) { - this.$emit(campaign, { - id: campaign.id, - summary: `New campaign created: ${campaign.name}`, - ts: Date.parse(campaign.createdAt), - }); - } - }, - async activate() { - const response = await this.selzy._makeRequest({ - method: "POST", - path: "/setHook", - data: { - api_key: this.selzy.$auth.api_key, - hook_url: this.http.endpoint, - event_format: "json_post", - events: [ - "campaign_status", - ], - status: "active", - }, - }); - this._setWebhookId(response.hook_id); - }, - async deactivate() { - const webhookId = this._getWebhookId(); - if (webhookId) { - await this.selzy._makeRequest({ - method: "POST", - path: "/removeHook", - data: { - hook_id: webhookId, - }, - }); - } - }, - }, - async run(event) { - const signature = event.headers["x-selzy-signature"]; - const secret = this.selzy.$auth.api_key; - const computedSignature = crypto.createHmac("sha256", secret) - .update(event.body) - .digest("base64"); - - if (computedSignature !== signature) { - this.http.respond({ - status: 401, - body: "Unauthorized", - }); - return; - } - - const { events_by_user } = event.body; - for (const userEvents of events_by_user) { - for (const eventData of userEvents.Events) { - if (eventData.event_name === "campaign_status") { - this.$emit(eventData, { - id: eventData.event_data.campaign_id, - summary: `New campaign status: ${eventData.event_data.status}`, - ts: Date.parse(eventData.event_time), - }); - } - } - } - - this.http.respond({ - status: 200, - body: "OK", - }); - }, -}; diff --git a/components/selzy/sources/new-campaign-status-instant/new-campaign-status-instant.mjs b/components/selzy/sources/new-campaign-status-instant/new-campaign-status-instant.mjs index cc4eff0ea5f4f..4f564b4cbe9b0 100644 --- a/components/selzy/sources/new-campaign-status-instant/new-campaign-status-instant.mjs +++ b/components/selzy/sources/new-campaign-status-instant/new-campaign-status-instant.mjs @@ -1,117 +1,24 @@ -import selzy from "../../selzy.app.mjs"; -import crypto from "crypto"; -import { axios } from "@pipedream/platform"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "selzy-new-campaign-status-instant", - name: "New Campaign Status Instant", - description: "Emit a new event when the status of a campaign changes. [See the documentation](https://selzy.com/en/support/api/common/event-notification-system-webhooks/)", - version: "0.0.{{ts}}", + name: "New Campaign Status (Instant)", + description: "Emit new event when the status of a campaign changes.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - selzy, - http: { - type: "$.interface.http", - customResponse: true, - }, - db: "$.service.db", - campaignId: { - propDefinition: [ - selzy, - "campaignId", - ], - }, - }, methods: { - _getWebhookId() { - return this.db.get("webhookId"); - }, - _setWebhookId(id) { - this.db.set("webhookId", id); - }, - _generateAuth(authBody) { - return crypto.createHash("md5").update(authBody) - .digest("hex"); - }, - }, - hooks: { - async deploy() { - const recentCampaigns = await this.selzy.getCampaigns({ - paginate: true, - max: 50, - }); - for (const campaign of recentCampaigns) { - this.$emit(campaign, { - id: campaign.id, - summary: `New campaign status: ${campaign.status}`, - ts: new Date(campaign.modified).getTime(), - }); - } - }, - async activate() { - const hookUrl = this.http.endpoint; - const hookId = await this.selzy._makeRequest({ - method: "POST", - path: "/setHook", - data: { - api_key: this.selzy.$auth.api_key, - hook_url: hookUrl, - event_format: "json_post", - events: { - campaign_status: this.campaignId - ? [ - this.campaignId, - ] - : [ - "*", - ], - }, - single_event: 0, - status: "active", - }, - }); - this._setWebhookId(hookId); + ...common.methods, + getEventType() { + return { + "events[campaign_status]": "*", + }; }, - async deactivate() { - const hookId = this._getWebhookId(); - await this.selzy._makeRequest({ - method: "POST", - path: "/deleteHook", - data: { - api_key: this.selzy.$auth.api_key, - id: hookId, - }, - }); + getSummary(body) { + return `Campaign status updated to ${body.status}`; }, }, - async run(event) { - const signature = event.headers["selzy-signature"]; - const computedSignature = this._generateAuth(JSON.stringify(event.body).replace(event.body.auth, this.selzy.$auth.api_key)); - - if (signature !== computedSignature) { - this.http.respond({ - status: 401, - body: "Unauthorized", - }); - return; - } - - this.http.respond({ - status: 200, - }); - - const { events_by_user } = event.body; - for (const userEvents of events_by_user) { - for (const eventData of userEvents.Events) { - if (eventData.event_name === "campaign_status") { - this.$emit(eventData.event_data, { - id: `${eventData.event_time}-${eventData.event_data.campaign_id}`, - summary: `Campaign status updated to ${eventData.event_data.status}`, - ts: new Date(eventData.event_time).getTime(), - }); - } - } - } - }, + sampleEmit, }; diff --git a/components/selzy/sources/new-campaign-status-instant/test-event.mjs b/components/selzy/sources/new-campaign-status-instant/test-event.mjs new file mode 100644 index 0000000000000..f6f7bbf53cef6 --- /dev/null +++ b/components/selzy/sources/new-campaign-status-instant/test-event.mjs @@ -0,0 +1,13 @@ +export default { + "auth": "974d2fec2b89aeb00195ab4419371b09", + "login": "ID7208007", + "event_name": "campaign_status", + "event_time": "2025-05-16 19:55:56", + "campaign_id": 326794044, + "status": "canceled", + "contact_count": 0, + "period_messages": 0, + "prepaid_messages": 0, + "pay_sum": 0, + "currency": "USD" +} \ No newline at end of file diff --git a/components/selzy/sources/new-campaign/new-campaign.mjs b/components/selzy/sources/new-campaign/new-campaign.mjs new file mode 100644 index 0000000000000..4516b882c8218 --- /dev/null +++ b/components/selzy/sources/new-campaign/new-campaign.mjs @@ -0,0 +1,78 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import selzy from "../../selzy.app.mjs"; + +export default { + key: "selzy-new-campaign", + name: "New Campaign Created", + description: "Emit new event when a new email campaign is created. Useful for monitoring campaign creation activity.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + selzy, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastId() { + return this.db.get("lastId") || 0; + }, + _setLastId(lastId) { + this.db.set("lastId", lastId); + }, + _getLastDate() { + return this.db.get("lastDate") || "1970-01-01 00:00:00"; + }, + _setLastDate(lastDate) { + this.db.set("lastDate", lastDate); + }, + async emitEvent(maxResults = false) { + const lastId = this._getLastId(); + const lastDate = this._getLastDate(); + + const response = this.selzy.paginate({ + fn: this.selzy.getCampaigns, + params: { + from: lastDate, + }, + }); + + let responseArray = []; + for await (const item of response) { + responseArray.push(item); + } + + if (responseArray.length) { + responseArray = responseArray.filter((item) => item.id > lastId); + responseArray = responseArray.reverse(); + + if (maxResults && (responseArray.length > maxResults)) { + responseArray.length = maxResults; + } + this._setLastId(responseArray[0].id); + this._setLastDate(responseArray[0].start_time); + } + + for (const item of responseArray.reverse()) { + this.$emit(item, { + id: item.id, + summary: `New campaign created: ${item.id}`, + ts: Date.parse(new Date()), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/selzy/sources/new-campaign/test-event.mjs b/components/selzy/sources/new-campaign/test-event.mjs new file mode 100644 index 0000000000000..071275e8d2e07 --- /dev/null +++ b/components/selzy/sources/new-campaign/test-event.mjs @@ -0,0 +1,11 @@ +export default { + "id": 326794048, + "start_time": "2025-06-10 18:12:00", + "status": "waits_schedule", + "message_id": 230193688, + "list_id": 1, + "subject": "Subject Name", + "sender_name": "Sender Name", + "sender_email": "sender@email.com.br", + "stats_url": "https://api.selzy.com/en/v5/campaigns/123456789" +} \ No newline at end of file diff --git a/components/selzy/sources/new-subscriber-instant/new-subscriber-instant.mjs b/components/selzy/sources/new-subscriber-instant/new-subscriber-instant.mjs index a8e2988f2470a..a5a65941848d1 100644 --- a/components/selzy/sources/new-subscriber-instant/new-subscriber-instant.mjs +++ b/components/selzy/sources/new-subscriber-instant/new-subscriber-instant.mjs @@ -1,126 +1,37 @@ -import selzy from "../../selzy.app.mjs"; -import crypto from "crypto"; -import { axios } from "@pipedream/platform"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "selzy-new-subscriber-instant", - name: "New Subscriber Instant", - description: "Emit a new event when a new contact subscribes to a specified list. [See the documentation](https://selzy.com/en/support/api/common/event-notification-system-webhooks/)", - version: "0.0.{{ts}}", + name: "New Subscriber (Instant)", + description: "Emit new event when a new contact subscribes to a specified list.", + version: "0.0.1", type: "source", dedupe: "unique", props: { - selzy, - http: { - type: "$.interface.http", - customResponse: true, - }, - db: "$.service.db", + ...common.props, listId: { propDefinition: [ - selzy, + common.props.selzy, "listId", ], + description: "Code of the list you want to monitor.", + optional: true, }, }, methods: { - _getWebhookId() { - return this.db.get("webhookId"); - }, - _setWebhookId(id) { - this.db.set("webhookId", id); - }, - _generateAuth(body, apiKey) { - const modifiedBody = { - ...body, - auth: apiKey, + ...common.methods, + getEventType() { + return { + "events[subscribe]": this.listId + ? this.listId + : "*", }; - return crypto.createHash("md5").update(JSON.stringify(modifiedBody)) - .digest("hex"); - }, - _verifySignature(body, expectedAuth) { - const actualAuth = this._generateAuth(body, this.selzy.$auth.api_key); - return expectedAuth === actualAuth; - }, - async _createWebhook() { - const response = await this.selzy._makeRequest({ - method: "POST", - path: "/setHook", - data: { - api_key: this.selzy.$auth.api_key, - hook_url: this.http.endpoint, - events: { - "subscribe": this.listId, - }, - event_format: "json_post", - single_event: 1, - status: "active", - }, - }); - this._setWebhookId(response.hook_id); }, - async _removeWebhook() { - const webhookId = this._getWebhookId(); - if (webhookId) { - await this.selzy._makeRequest({ - method: "POST", - path: "/removeHook", - data: { - api_key: this.selzy.$auth.api_key, - hook_id: webhookId, - }, - }); - } + getSummary(body) { + return `New subscriber: ${body.email}`; }, }, - hooks: { - async deploy() { - const subscriptions = await this.selzy._makeRequest({ - path: "/subscriptions", - method: "GET", - }); - const recent = subscriptions.slice(-50); - for (const subscription of recent) { - this.$emit(subscription, { - id: subscription.email_id, - summary: `New subscriber: ${subscription.email}`, - ts: Date.parse(subscription.created_at), - }); - } - }, - async activate() { - await this._createWebhook(); - }, - async deactivate() { - await this._removeWebhook(); - }, - }, - async run(event) { - const { - auth, events_by_user, - } = event.body; - const isValid = this._verifySignature(event.body, auth); - if (!isValid) { - this.http.respond({ - status: 401, - body: "Unauthorized", - }); - return; - } - for (const userEvents of events_by_user) { - for (const eventInfo of userEvents.Events) { - if (eventInfo.event_name === "subscribe" && eventInfo.event_data.just_subscribed_list_ids.includes(this.listId)) { - this.$emit(eventInfo.event_data, { - id: eventInfo.event_data.email, - summary: `New subscriber: ${eventInfo.event_data.email}`, - ts: Date.parse(eventInfo.event_time), - }); - } - } - } - this.http.respond({ - status: 200, - body: "OK", - }); - }, + sampleEmit, }; diff --git a/components/selzy/sources/new-subscriber-instant/test-event.mjs b/components/selzy/sources/new-subscriber-instant/test-event.mjs new file mode 100644 index 0000000000000..5bae6989e986a --- /dev/null +++ b/components/selzy/sources/new-subscriber-instant/test-event.mjs @@ -0,0 +1,10 @@ +export default { + "auth": "21f6bf566f216300c5a6080ecfa89b75", + "login": "ID2070078", + "event_name": "subscribe", + "event_time": "2025-05-16 20:23:28", + "email": "subscriber@email.com", + "subscribed_list_ids": [ + 3 + ] +} \ No newline at end of file From c27061e5c742593f1dcded1b0efd807d6c0ced5c Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 19 May 2025 10:04:18 -0300 Subject: [PATCH 03/10] pnpm update --- pnpm-lock.yaml | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 265297b272cae..4d36602edf46c 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: @@ -8561,8 +8560,7 @@ importers: specifier: ^1.5.1 version: 1.6.6 - components/neon_postgres: - specifiers: {} + components/neon_postgres: {} components/nerv: {} @@ -9598,8 +9596,7 @@ importers: specifier: ^3.0.3 version: 3.0.3 - components/pdforge: - specifiers: {} + components/pdforge: {} components/peach: dependencies: @@ -10876,8 +10873,7 @@ importers: components/renderform: {} - components/rendi: - specifiers: {} + components/rendi: {} components/rentcast: dependencies: @@ -11434,8 +11430,7 @@ importers: components/scrapein_: {} - components/scrapeless: - specifiers: {} + components/scrapeless: {} components/scrapeninja: dependencies: @@ -11553,7 +11548,11 @@ importers: components/sellsy: {} - components/selzy: {} + components/selzy: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/semaphore: {} @@ -15524,7 +15523,7 @@ importers: version: 3.1.7 ts-jest: specifier: ^29.2.5 - version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0))(typescript@5.7.2) + version: 29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0))(typescript@5.7.2) tsup: specifier: ^8.3.6 version: 8.3.6(@microsoft/api-extractor@7.47.12(@types/node@20.17.30))(jiti@1.21.6)(postcss@8.4.49)(tsx@4.19.4)(typescript@5.7.2)(yaml@2.6.1) @@ -15561,7 +15560,7 @@ importers: version: 3.1.0 jest: specifier: ^29.1.2 - version: 29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0) + version: 29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0) type-fest: specifier: ^4.15.0 version: 4.27.0 @@ -35649,6 +35648,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: @@ -49297,7 +49298,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0))(typescript@5.7.2): + ts-jest@29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0))(typescript@5.7.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -49311,10 +49312,10 @@ snapshots: typescript: 5.7.2 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.26.0 + '@babel/core': 8.0.0-alpha.13 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.0) + babel-jest: 29.7.0(@babel/core@8.0.0-alpha.13) esbuild: 0.24.2 ts-jest@29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.6.3): From 97067f705008df9243ae2e3e5b07628721d6321d Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 19 May 2025 10:09:56 -0300 Subject: [PATCH 04/10] pnpm update --- pnpm-lock.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d36602edf46c..83ed1e5e3debc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15523,7 +15523,7 @@ importers: version: 3.1.7 ts-jest: specifier: ^29.2.5 - version: 29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0))(typescript@5.7.2) + version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0))(typescript@5.7.2) tsup: specifier: ^8.3.6 version: 8.3.6(@microsoft/api-extractor@7.47.12(@types/node@20.17.30))(jiti@1.21.6)(postcss@8.4.49)(tsx@4.19.4)(typescript@5.7.2)(yaml@2.6.1) @@ -15560,7 +15560,7 @@ importers: version: 3.1.0 jest: specifier: ^29.1.2 - version: 29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0) + version: 29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0) type-fest: specifier: ^4.15.0 version: 4.27.0 @@ -49298,7 +49298,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0))(typescript@5.7.2): + ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0))(typescript@5.7.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -49312,10 +49312,10 @@ snapshots: typescript: 5.7.2 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 8.0.0-alpha.13 + '@babel/core': 7.26.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@8.0.0-alpha.13) + babel-jest: 29.7.0(@babel/core@7.26.0) esbuild: 0.24.2 ts-jest@29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.6.3): From 932e9c550d08c90beb976cda1c05709a309060ad Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 19 May 2025 10:24:45 -0300 Subject: [PATCH 05/10] some adjusts --- .../selzy/actions/create-campaign/create-campaign.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/selzy/actions/create-campaign/create-campaign.mjs b/components/selzy/actions/create-campaign/create-campaign.mjs index 29e088fa66edc..8a5cf9c944014 100644 --- a/components/selzy/actions/create-campaign/create-campaign.mjs +++ b/components/selzy/actions/create-campaign/create-campaign.mjs @@ -6,9 +6,9 @@ import { import selzy from "../../selzy.app.mjs"; export default { - key: "selzy-create-email-campaign", - name: "Create Email Campaign", - description: "Creates a new email campaign. [See the documentation](https://selzy.com/en/support/api/messages/createcampaign/)", + key: "selzy-create-campaign", + name: "Create Campaign", + description: "Creates a new campaign. [See the documentation](https://selzy.com/en/support/api/messages/createcampaign/)", version: "0.0.1", type: "action", props: { From 3ff4db297b6200fd2d12d08ac36cafdcdb5b9db7 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 23 May 2025 10:57:49 -0300 Subject: [PATCH 06/10] some adjusts --- .../actions/create-campaign/create-campaign.mjs | 12 +----------- .../create-email-message/create-email-message.mjs | 8 +++----- .../selzy/sources/new-campaign/new-campaign.mjs | 4 +++- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/components/selzy/actions/create-campaign/create-campaign.mjs b/components/selzy/actions/create-campaign/create-campaign.mjs index 8a5cf9c944014..dcaeff0aa4072 100644 --- a/components/selzy/actions/create-campaign/create-campaign.mjs +++ b/components/selzy/actions/create-campaign/create-campaign.mjs @@ -1,8 +1,5 @@ import { ConfigurationError } from "@pipedream/platform"; -import { - clearEmpty, - parseObject, -} from "../../common/utils.mjs"; +import { clearEmpty } from "../../common/utils.mjs"; import selzy from "../../selzy.app.mjs"; export default { @@ -37,12 +34,6 @@ export default { description: "To track whether there are any click-throughs in email messages, the default value is `false` (do not track). If `true`, all external links will be replaced with special ones that allow you to track the fact of a click-through, and then forward the user to the desired page. The **Track Links** argument is ignored for SMS messages.", optional: true, }, - contacts: { - type: "string[]", - label: "Contacts", - description: "Email addresses (or phone numbers for sms messages) to which sending of a message should be limited. If this argument is absent, sending will be made to all contacts on the list for which the message is made (possibly, taking into account segmentation by tags). If the contacts argument is present, only those contacts that are on the list will be taken into account, while the other will be ignored. If there are too many addresses (phone numbers) for sending in the contacts parameter, the **Contacts URL** parameter can be used instead. You can't set both parameters at the same time", - optional: true, - }, contactsUrl: { type: "string", label: "Contacts URL", @@ -114,7 +105,6 @@ export default { start_time: this.startTime, track_read: this.trackRead && +this.trackRead, track_links: this.trackLinks && +this.trackLinks, - contacts: parseObject(this.contacts)?.join(","), contacts_url: this.contactsUrl, track_ga: this.trackGa && +this.trackGa, ga_medium: this.gaMedium, diff --git a/components/selzy/actions/create-email-message/create-email-message.mjs b/components/selzy/actions/create-email-message/create-email-message.mjs index e0d92fdc571c6..573b69e95f006 100644 --- a/components/selzy/actions/create-email-message/create-email-message.mjs +++ b/components/selzy/actions/create-email-message/create-email-message.mjs @@ -26,13 +26,12 @@ export default { subject: { type: "string", label: "Subject", - description: "String with the letter subject. It may include [substitution fields](https://selzy.com/en/support/letter/other-functions/personalization-tags/). The parameter is optional if **Template Id** is indicated.", - optional: true, + description: "String with the letter subject. It may include substitution fields. If you wish to use substitution fields, specify a string within a Pipedream Custom Expression and escape the curly brackets with a backslash. For example: {{ \"Welcome to Our Newsletter, \\{\\{Name\\}\\}!\" }}. The parameter is optional if Template Id is indicated.", }, body: { type: "string", label: "Body", - description: "If you transfer the entire HTML text, test such letters additionally as headers outside the body may be modified. The parameter is optional if **Template Id** or **System Template Id** is indicated.", + description: "HTML body of the letter. It may include substitution fields. If you wish to use substitution fields, specify an HTML string within a Pipedream Custom Expression and escape the curly brackets with a backslash. For example: {{ \"

Hello \\{\\{Name\\}\\},

Here is your update.

\" }}.. The parameter is optional if **Template Id** or **System Template Id** is indicated.", }, listId: { propDefinition: [ @@ -43,14 +42,13 @@ export default { textBody: { type: "string", label: "Text Body", - description: "Text version of the template. It is absent by default. If you do not provide the text version along with the HTML version, you are recommended to set the **Generate Text** parameter to 1 for automatic generation of the text part of the letter.", + description: "Text body of the letter. It may include substitution fields. If you wish to use substitution fields, specify a text string within a Pipedream Custom Expression and escape the curly brackets with a backslash. For example: {{ \"Hello \\{\\{Name\\}\\},\\nHere is your update.\" }}.", optional: true, }, generateText: { type: "boolean", label: "Generate Text", description: "`True` means that the text part of the letter will be generated automatically based on the HTML part. If you do not provide the text version along with the HTML version, you are recommended to set the **Generate Text** parameter to `true` for automatic generation of the text part of the letter. If the text variant of the letter is provided using the **Text Body** parameter, the **Generate Text** parameter is ignored. Thus, if the **Generate Text** value has been set to `true`, the server's response will contain a warning.", - optional: true, }, rawBody: { type: "string", diff --git a/components/selzy/sources/new-campaign/new-campaign.mjs b/components/selzy/sources/new-campaign/new-campaign.mjs index 4516b882c8218..11b140e839ad6 100644 --- a/components/selzy/sources/new-campaign/new-campaign.mjs +++ b/components/selzy/sources/new-campaign/new-campaign.mjs @@ -47,13 +47,15 @@ export default { responseArray.push(item); } + responseArray = responseArray.filter((item) => item.id > lastId); + if (responseArray.length) { - responseArray = responseArray.filter((item) => item.id > lastId); responseArray = responseArray.reverse(); if (maxResults && (responseArray.length > maxResults)) { responseArray.length = maxResults; } + this._setLastId(responseArray[0].id); this._setLastDate(responseArray[0].start_time); } From 6a90ec59e2b677a0e19694de0c54c01a2d956753 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 26 May 2025 12:48:50 -0300 Subject: [PATCH 07/10] some adjusts --- .../create-email-message/create-email-message.mjs | 6 +++--- .../selzy/sources/new-campaign/new-campaign.mjs | 15 +-------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/components/selzy/actions/create-email-message/create-email-message.mjs b/components/selzy/actions/create-email-message/create-email-message.mjs index 573b69e95f006..f01d545b649e8 100644 --- a/components/selzy/actions/create-email-message/create-email-message.mjs +++ b/components/selzy/actions/create-email-message/create-email-message.mjs @@ -26,12 +26,12 @@ export default { subject: { type: "string", label: "Subject", - description: "String with the letter subject. It may include substitution fields. If you wish to use substitution fields, specify a string within a Pipedream Custom Expression and escape the curly brackets with a backslash. For example: {{ \"Welcome to Our Newsletter, \\{\\{Name\\}\\}!\" }}. The parameter is optional if Template Id is indicated.", + description: "String with the letter subject. It may include substitution fields. If you wish to use substitution fields, specify a string within a Pipedream Custom Expression and escape the curly brackets with a backslash. For example: `{{ \"Welcome to Our Newsletter, \\{\\{Name\\}\\}!\" }}`. The parameter is optional if Template Id is indicated.", }, body: { type: "string", label: "Body", - description: "HTML body of the letter. It may include substitution fields. If you wish to use substitution fields, specify an HTML string within a Pipedream Custom Expression and escape the curly brackets with a backslash. For example: {{ \"

Hello \\{\\{Name\\}\\},

Here is your update.

\" }}.. The parameter is optional if **Template Id** or **System Template Id** is indicated.", + description: "HTML body of the letter. It may include substitution fields. If you wish to use substitution fields, specify an HTML string within a Pipedream Custom Expression and escape the curly brackets with a backslash. For example: `{{ \"

Hello \\{\\{Name\\}\\},

Here is your update.

\" }}`.", }, listId: { propDefinition: [ @@ -42,7 +42,7 @@ export default { textBody: { type: "string", label: "Text Body", - description: "Text body of the letter. It may include substitution fields. If you wish to use substitution fields, specify a text string within a Pipedream Custom Expression and escape the curly brackets with a backslash. For example: {{ \"Hello \\{\\{Name\\}\\},\\nHere is your update.\" }}.", + description: "Text body of the letter. It may include substitution fields. If you wish to use substitution fields, specify a text string within a Pipedream Custom Expression and escape the curly brackets with a backslash. For example: `{{ \"Hello \\{\\{Name\\}\\},\\nHere is your update.\" }}`.", optional: true, }, generateText: { diff --git a/components/selzy/sources/new-campaign/new-campaign.mjs b/components/selzy/sources/new-campaign/new-campaign.mjs index 11b140e839ad6..dcb4309a06eb3 100644 --- a/components/selzy/sources/new-campaign/new-campaign.mjs +++ b/components/selzy/sources/new-campaign/new-campaign.mjs @@ -25,21 +25,11 @@ export default { _setLastId(lastId) { this.db.set("lastId", lastId); }, - _getLastDate() { - return this.db.get("lastDate") || "1970-01-01 00:00:00"; - }, - _setLastDate(lastDate) { - this.db.set("lastDate", lastDate); - }, async emitEvent(maxResults = false) { const lastId = this._getLastId(); - const lastDate = this._getLastDate(); const response = this.selzy.paginate({ fn: this.selzy.getCampaigns, - params: { - from: lastDate, - }, }); let responseArray = []; @@ -47,17 +37,14 @@ export default { responseArray.push(item); } - responseArray = responseArray.filter((item) => item.id > lastId); + responseArray = responseArray.filter((item) => item.id > lastId).sort((a, b) => b.id - a.id); if (responseArray.length) { - responseArray = responseArray.reverse(); - if (maxResults && (responseArray.length > maxResults)) { responseArray.length = maxResults; } this._setLastId(responseArray[0].id); - this._setLastDate(responseArray[0].start_time); } for (const item of responseArray.reverse()) { From b0acddc1f6df264aa953e8416d4675d9eef8f7e4 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 26 May 2025 13:41:26 -0300 Subject: [PATCH 08/10] Update components/selzy/actions/create-campaign/create-campaign.mjs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- components/selzy/actions/create-campaign/create-campaign.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/selzy/actions/create-campaign/create-campaign.mjs b/components/selzy/actions/create-campaign/create-campaign.mjs index dcaeff0aa4072..5e442c096e979 100644 --- a/components/selzy/actions/create-campaign/create-campaign.mjs +++ b/components/selzy/actions/create-campaign/create-campaign.mjs @@ -103,8 +103,8 @@ export default { params: clearEmpty({ message_id: this.messageId, start_time: this.startTime, - track_read: this.trackRead && +this.trackRead, - track_links: this.trackLinks && +this.trackLinks, + track_read: this.trackRead ? 1 : 0, + track_links: this.trackLinks ? 1 : 0, contacts_url: this.contactsUrl, track_ga: this.trackGa && +this.trackGa, ga_medium: this.gaMedium, From 098d58eb512d2dda4f74931c4894d6176dbe77c8 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 26 May 2025 13:41:46 -0300 Subject: [PATCH 09/10] Update components/selzy/sources/new-campaign/new-campaign.mjs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- components/selzy/sources/new-campaign/new-campaign.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/selzy/sources/new-campaign/new-campaign.mjs b/components/selzy/sources/new-campaign/new-campaign.mjs index dcb4309a06eb3..9eb268434d84c 100644 --- a/components/selzy/sources/new-campaign/new-campaign.mjs +++ b/components/selzy/sources/new-campaign/new-campaign.mjs @@ -51,7 +51,7 @@ export default { this.$emit(item, { id: item.id, summary: `New campaign created: ${item.id}`, - ts: Date.parse(new Date()), + ts: Date.now(), }); } }, From 2689a54197520e0cb423e3235f179e3648be2549 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Tue, 27 May 2025 09:26:25 -0300 Subject: [PATCH 10/10] lint fix --- .../selzy/actions/create-campaign/create-campaign.mjs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/components/selzy/actions/create-campaign/create-campaign.mjs b/components/selzy/actions/create-campaign/create-campaign.mjs index 5e442c096e979..0223a8c96820b 100644 --- a/components/selzy/actions/create-campaign/create-campaign.mjs +++ b/components/selzy/actions/create-campaign/create-campaign.mjs @@ -103,8 +103,12 @@ export default { params: clearEmpty({ message_id: this.messageId, start_time: this.startTime, - track_read: this.trackRead ? 1 : 0, - track_links: this.trackLinks ? 1 : 0, + track_read: this.trackRead + ? 1 + : 0, + track_links: this.trackLinks + ? 1 + : 0, contacts_url: this.contactsUrl, track_ga: this.trackGa && +this.trackGa, ga_medium: this.gaMedium,