From 2a63a63632d86526c184f1598c7dc4a4256ae7f1 Mon Sep 17 00:00:00 2001 From: Michelle Bergeron Date: Mon, 3 Nov 2025 09:33:56 -0500 Subject: [PATCH 1/5] wip --- .../actions/add-contact/add-contact.mjs | 2 +- .../create-campaign/create-campaign.mjs | 120 ++++++++++++++++++ .../actions/create-contact/create-contact.mjs | 19 +++ .../actions/create-segment/create-segment.mjs | 19 +++ .../actions/list-campaigns/list-campaigns.mjs | 49 +++++++ .../actions/list-contacts/list-contacts.mjs | 42 ++++++ .../actions/send-email/send-email.mjs | 2 +- .../unsubscribe-contact.mjs | 2 +- .../update-campaign/update-campaign.mjs | 19 +++ .../actions/update-contact/update-contact.mjs | 19 +++ .../elastic_email/elastic_email.app.mjs | 113 ++++++++++++++++- components/elastic_email/package.json | 4 +- .../new-delivery-event/new-delivery-event.mjs | 0 13 files changed, 403 insertions(+), 7 deletions(-) create mode 100644 components/elastic_email/actions/create-campaign/create-campaign.mjs create mode 100644 components/elastic_email/actions/create-contact/create-contact.mjs create mode 100644 components/elastic_email/actions/create-segment/create-segment.mjs create mode 100644 components/elastic_email/actions/list-campaigns/list-campaigns.mjs create mode 100644 components/elastic_email/actions/list-contacts/list-contacts.mjs create mode 100644 components/elastic_email/actions/update-campaign/update-campaign.mjs create mode 100644 components/elastic_email/actions/update-contact/update-contact.mjs create mode 100644 components/elastic_email/sources/new-delivery-event/new-delivery-event.mjs diff --git a/components/elastic_email/actions/add-contact/add-contact.mjs b/components/elastic_email/actions/add-contact/add-contact.mjs index 0829aead7ed08..f030bb01dbd0e 100644 --- a/components/elastic_email/actions/add-contact/add-contact.mjs +++ b/components/elastic_email/actions/add-contact/add-contact.mjs @@ -10,7 +10,7 @@ export default { key: "elastic_email-add-contact", name: "Add Contact to Mailing List", description: "Adds a new contact to a mailing list. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/contactsPost)", - version: "0.0.2", + version: "0.0.3", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/elastic_email/actions/create-campaign/create-campaign.mjs b/components/elastic_email/actions/create-campaign/create-campaign.mjs new file mode 100644 index 0000000000000..ac9e3d6aa48b7 --- /dev/null +++ b/components/elastic_email/actions/create-campaign/create-campaign.mjs @@ -0,0 +1,120 @@ +import app from "../../elastic_email.app.mjs"; +import { ConfigurationError } from "@pipedream/platform"; + +export default { + key: "elastic_email-create-campaign", + name: "Create Campaign", + description: "Create a campaign in an Elastic Email account. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/campaignsPost)", + version: "0.0.{{ts}}", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + app, + name: { + type: "string", + label: "Campaign Name", + description: "The name of the campaign", + }, + from: { + type: "string", + label: "From", + description: "Your e-mail with an optional name (e.g.: John Doe email@domain.com)", + }, + recipientListNames: { + propDefinition: [ + app, + "listNames", + ], + label: "Recipient List Names", + description: "Names of lists from your Account to read recipients from", + }, + recipientSegmentNames: { + propDefinition: [ + app, + "segmentNames", + ], + label: "Recipient Segment Names", + description: "Names of segments from your Account to read recipients from", + optional: true, + }, + replyTo: { + type: "string", + label: "Reply To", + description: "To what address should the recipients reply to (e.g. John Doe email@domain.com)", + optional: true, + }, + subject: { + type: "string", + label: "Subject", + description: "Default subject of email", + optional: true, + }, + templateName: { + propDefinition: [ + app, + "templateName", + ], + optional: true, + }, + status: { + propDefinition: [ + app, + "campaignStatus", + ], + }, + excludeRecipientListNames: { + propDefinition: [ + app, + "listNames", + ], + label: "Exclude Recipient List Names", + description: "Names of lists from your Account to exclude from the campaign", + }, + excludeRecipientSegmentNames: { + propDefinition: [ + app, + "segmentNames", + ], + label: "Exclude Recipient Segment Names", + description: "Names of segments from your Account to exclude from the campaign", + optional: true, + }, + }, + async run({ $ }) { + if (!this.recipientListNames && !this.recipientSegmentNames) { + throw new ConfigurationError("You must provide at least one list or segment to read recipients from"); + } + + const response = await this.app.createCampaign({ + $, + data: { + Name: this.name, + Recipients: { + ListNames: this.recipientListNames, + SegmentNames: this.recipientSegmentNames, + }, + Content: [ + { + From: this.from, + ReplyTo: this.replyTo, + Subject: this.subject, + TemplateName: this.templateName, + }, + ], + Status: this.status, + ExcludeRecipients: this.excludeRecipientListNames || this.excludeRecipientSegmentNames + ? { + ListNames: this.excludeRecipientListNames, + SegmentNames: this.excludeRecipientSegmentNames, + } + : undefined, + }, + }); + $.export("$summary", "Campaign created successfully"); + return response; + }, +}; diff --git a/components/elastic_email/actions/create-contact/create-contact.mjs b/components/elastic_email/actions/create-contact/create-contact.mjs new file mode 100644 index 0000000000000..2a4f957f1229a --- /dev/null +++ b/components/elastic_email/actions/create-contact/create-contact.mjs @@ -0,0 +1,19 @@ +import app from "../../elasticemail.app.mjs"; + +export default { + key: "elastic_email-create-contact", + name: "Create Contact", + description: "Create a contact in an Elastic Email account. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/contactsPost)", + version: "0.0.{{ts}}", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + app, + }, + async run() { + }, +}; diff --git a/components/elastic_email/actions/create-segment/create-segment.mjs b/components/elastic_email/actions/create-segment/create-segment.mjs new file mode 100644 index 0000000000000..7b27b50ea8c80 --- /dev/null +++ b/components/elastic_email/actions/create-segment/create-segment.mjs @@ -0,0 +1,19 @@ +import app from "../../elastic_email.app.mjs"; + +export default { + key: "elastic_email-create-segment", + name: "Create Segment", + description: "Create a segment in an Elastic Email account. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#tag/Segments)", + version: "0.0.{{ts}}", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + app, + }, + async run() { + }, +}; diff --git a/components/elastic_email/actions/list-campaigns/list-campaigns.mjs b/components/elastic_email/actions/list-campaigns/list-campaigns.mjs new file mode 100644 index 0000000000000..748f7d46c49ff --- /dev/null +++ b/components/elastic_email/actions/list-campaigns/list-campaigns.mjs @@ -0,0 +1,49 @@ +import app from "../../elastic_email.app.mjs"; + +export default { + key: "elastic_email-list-campaigns", + name: "List Campaigns", + description: "List campaigns in an Elastic Email account. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/campaignsGet)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + app, + search: { + type: "string", + label: "Search", + description: "The search query to filter campaigns", + optional: true, + }, + limit: { + type: "integer", + label: "Limit", + description: "The maximum number of campaigns to return", + default: 100, + optional: true, + }, + offset: { + type: "integer", + label: "Offset", + description: "The offset to start from", + default: 0, + optional: true, + }, + }, + async run({ $ }) { + const response = await this.app.listCampaigns({ + $, + params: { + search: this.search, + limit: this.limit, + offset: this.offset, + }, + }); + $.export("$summary", `Successfully listed ${response?.length} campaigns.`); + return response; + }, +}; diff --git a/components/elastic_email/actions/list-contacts/list-contacts.mjs b/components/elastic_email/actions/list-contacts/list-contacts.mjs new file mode 100644 index 0000000000000..29ebd37392a8d --- /dev/null +++ b/components/elastic_email/actions/list-contacts/list-contacts.mjs @@ -0,0 +1,42 @@ +import app from "../../elastic_email.app.mjs"; + +export default { + key: "elastic_email-list-contacts", + name: "List Contacts", + description: "List contacts in an Elastic Email account. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/contactsGet)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + app, + limit: { + type: "integer", + label: "Limit", + description: "The maximum number of contacts to return", + default: 100, + optional: true, + }, + offset: { + type: "integer", + label: "Offset", + description: "The offset to start from", + default: 0, + optional: true, + }, + }, + async run({ $ }) { + const response = await this.app.listContacts({ + $, + params: { + limit: this.limit, + offset: this.offset, + }, + }); + $.export("$summary", `Successfully listed ${response?.length} contacts.`); + return response; + }, +}; diff --git a/components/elastic_email/actions/send-email/send-email.mjs b/components/elastic_email/actions/send-email/send-email.mjs index 528d87f4e6992..aaecbc71451f8 100644 --- a/components/elastic_email/actions/send-email/send-email.mjs +++ b/components/elastic_email/actions/send-email/send-email.mjs @@ -9,7 +9,7 @@ export default { key: "elastic_email-send-email", name: "Send Email", description: "Sends an email to one or more recipients. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/emailsPost)", - version: "0.0.2", + version: "0.0.3", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/elastic_email/actions/unsubscribe-contact/unsubscribe-contact.mjs b/components/elastic_email/actions/unsubscribe-contact/unsubscribe-contact.mjs index 1447d9f636b56..abe8dd6d24e44 100644 --- a/components/elastic_email/actions/unsubscribe-contact/unsubscribe-contact.mjs +++ b/components/elastic_email/actions/unsubscribe-contact/unsubscribe-contact.mjs @@ -5,7 +5,7 @@ export default { key: "elastic_email-unsubscribe-contact", name: "Unsubscribe Contact", description: "Unsubscribes a contact from future emails. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/suppressionsUnsubscribesPost)", - version: "0.0.2", + version: "0.0.3", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/elastic_email/actions/update-campaign/update-campaign.mjs b/components/elastic_email/actions/update-campaign/update-campaign.mjs new file mode 100644 index 0000000000000..42c82445d4a69 --- /dev/null +++ b/components/elastic_email/actions/update-campaign/update-campaign.mjs @@ -0,0 +1,19 @@ +import app from "../../elastic_email.app.mjs"; + +export default { + key: "elastic_email-update-campaign", + name: "Update Campaign", + description: "Update a campaign in an Elastic Email account. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/campaignsByNamePut)", + version: "0.0.{{ts}}", + type: "action", + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + app, + }, + async run() { + }, +}; diff --git a/components/elastic_email/actions/update-contact/update-contact.mjs b/components/elastic_email/actions/update-contact/update-contact.mjs new file mode 100644 index 0000000000000..54808a06c320c --- /dev/null +++ b/components/elastic_email/actions/update-contact/update-contact.mjs @@ -0,0 +1,19 @@ +import app from "../../elastic_email.app.mjs"; + +export default { + key: "elastic_email-update-contact", + name: "Update Contact", + description: "Update a contact in an Elastic Email account. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/contactsByEmailPut)", + version: "0.0.{{ts}}", + type: "action", + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + app, + }, + async run() { + }, +}; diff --git a/components/elastic_email/elastic_email.app.mjs b/components/elastic_email/elastic_email.app.mjs index f9e25b70be3ea..a5d4d3c76b395 100644 --- a/components/elastic_email/elastic_email.app.mjs +++ b/components/elastic_email/elastic_email.app.mjs @@ -29,7 +29,7 @@ export default { templateName: { type: "string", label: "Template Name", - description: "The name of template.", + description: "The name of template", async options({ page }) { const data = await this.listTemplates({ params: { @@ -47,6 +47,64 @@ export default { label: "Email Addresses", description: "A list of email addresses to unsubscribe", }, + campaign: { + type: "string", + label: "Campaign", + description: "The name of a campaign", + async options({ page }) { + const { campaigns } = await this.listCampaigns({ + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + return campaigns?.map(({ Name: name }) => name) || []; + }, + }, + contact: { + type: "string", + label: "Contact", + description: "The email address of a contact", + async options({ page }) { + const { contacts } = await this.listContacts({ + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + return contacts?.map(({ Email: email }) => email) || []; + }, + }, + segmentNames: { + type: "string[]", + label: "Segment Names", + description: "The name of a segment", + async options({ page }) { + const { segments } = await this.listSegments({ + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + return segments?.map(({ Name: name }) => name) || []; + }, + }, + campaignStatus: { + type: "string", + label: "Campaign Status", + description: "The status of a campaign", + options: [ + "Deleted", + "Active", + "Processing", + "Sending", + "Completed", + "Paused", + "Cancelled", + "Draft", + ], + optional: true, + }, }, methods: { _baseUrl() { @@ -61,7 +119,7 @@ export default { $ = this, path, ...opts }) { return axios($, { - url: this._baseUrl() + path, + url: `${this._baseUrl()}${path}`, headers: this._headers(), ...opts, }); @@ -90,6 +148,18 @@ export default { ...opts, }); }, + listCampaigns(opts = {}) { + return this._makeRequest({ + path: "/campaigns", + ...opts, + }); + }, + listSegments(opts = {}) { + return this._makeRequest({ + path: "/segments", + ...opts, + }); + }, sendBulkEmails(opts = {}) { return this._makeRequest({ method: "POST", @@ -111,6 +181,45 @@ export default { ...opts, }); }, + createCampaign(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/campaigns", + ...opts, + }); + }, + updateCampaign({ + campaign, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/campaigns/${campaign}`, + ...opts, + }); + }, + createContact(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/contacts", + ...opts, + }); + }, + updateContact({ + contact, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/contacts/${contact}`, + ...opts, + }); + }, + createSegment(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/segments", + ...opts, + }); + }, async *paginate({ fn, params = {}, maxResults = null, ...opts }) { diff --git a/components/elastic_email/package.json b/components/elastic_email/package.json index aef531c563c61..db41de6785ebb 100644 --- a/components/elastic_email/package.json +++ b/components/elastic_email/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/elastic_email", - "version": "0.1.0", + "version": "0.2.0", "description": "Pipedream Elastic Email Components", "main": "elastic_email.app.mjs", "keywords": [ @@ -13,6 +13,6 @@ "access": "public" }, "dependencies": { - "@pipedream/platform": "^3.0.3" + "@pipedream/platform": "^3.1.0" } } diff --git a/components/elastic_email/sources/new-delivery-event/new-delivery-event.mjs b/components/elastic_email/sources/new-delivery-event/new-delivery-event.mjs new file mode 100644 index 0000000000000..e69de29bb2d1d From 6ed10ea1c4f9b48b10a99e7ce8c881d0bf0853c7 Mon Sep 17 00:00:00 2001 From: Michelle Bergeron Date: Mon, 3 Nov 2025 15:19:50 -0500 Subject: [PATCH 2/5] new components --- .../create-campaign/create-campaign.mjs | 15 ++- .../actions/create-contact/create-contact.mjs | 50 +++++++- .../actions/create-segment/create-segment.mjs | 23 +++- .../update-campaign/update-campaign.mjs | 119 +++++++++++++++++- .../actions/update-contact/update-contact.mjs | 40 +++++- .../elastic_email/elastic_email.app.mjs | 30 ++++- .../new-delivery-event/new-delivery-event.mjs | 44 +++++++ 7 files changed, 301 insertions(+), 20 deletions(-) diff --git a/components/elastic_email/actions/create-campaign/create-campaign.mjs b/components/elastic_email/actions/create-campaign/create-campaign.mjs index ac9e3d6aa48b7..cbd5ab40c0cbd 100644 --- a/components/elastic_email/actions/create-campaign/create-campaign.mjs +++ b/components/elastic_email/actions/create-campaign/create-campaign.mjs @@ -5,7 +5,7 @@ export default { key: "elastic_email-create-campaign", name: "Create Campaign", description: "Create a campaign in an Elastic Email account. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/campaignsPost)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", annotations: { destructiveHint: false, @@ -45,7 +45,12 @@ export default { type: "string", label: "Reply To", description: "To what address should the recipients reply to (e.g. John Doe email@domain.com)", - optional: true, + }, + status: { + propDefinition: [ + app, + "campaignStatus", + ], }, subject: { type: "string", @@ -60,12 +65,6 @@ export default { ], optional: true, }, - status: { - propDefinition: [ - app, - "campaignStatus", - ], - }, excludeRecipientListNames: { propDefinition: [ app, diff --git a/components/elastic_email/actions/create-contact/create-contact.mjs b/components/elastic_email/actions/create-contact/create-contact.mjs index 2a4f957f1229a..cf379221e04b3 100644 --- a/components/elastic_email/actions/create-contact/create-contact.mjs +++ b/components/elastic_email/actions/create-contact/create-contact.mjs @@ -1,10 +1,11 @@ -import app from "../../elasticemail.app.mjs"; +import app from "../../elastic_email.app.mjs"; +import { parseObject } from "../../common/utils.mjs"; export default { key: "elastic_email-create-contact", name: "Create Contact", description: "Create a contact in an Elastic Email account. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/contactsPost)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", annotations: { destructiveHint: false, @@ -13,7 +14,50 @@ export default { }, props: { app, + email: { + type: "string", + label: "Email", + description: "The email of the contact", + }, + status: { + propDefinition: [ + app, + "contactStatus", + ], + }, + firstName: { + type: "string", + label: "First Name", + description: "The first name of the contact", + optional: true, + }, + lastName: { + type: "string", + label: "Last Name", + description: "The last name of the contact", + optional: true, + }, + customFields: { + type: "object", + label: "Custom Fields", + description: "A key-value collection of custom contact fields which can be used in the system. Only already existing custom fields will be saved.", + optional: true, + }, }, - async run() { + async run({ $ }) { + const response = await this.app.createContact({ + $, + data: [ + { + Email: this.email, + Status: this.status, + FirstName: this.firstName, + LastName: this.lastName, + CustomFields: parseObject(this.customFields), + }, + ], + }); + $.export("$summary", "Contact created successfully"); + return response; }, }; diff --git a/components/elastic_email/actions/create-segment/create-segment.mjs b/components/elastic_email/actions/create-segment/create-segment.mjs index 7b27b50ea8c80..ca4df17566cc0 100644 --- a/components/elastic_email/actions/create-segment/create-segment.mjs +++ b/components/elastic_email/actions/create-segment/create-segment.mjs @@ -4,7 +4,7 @@ export default { key: "elastic_email-create-segment", name: "Create Segment", description: "Create a segment in an Elastic Email account. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#tag/Segments)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", annotations: { destructiveHint: false, @@ -13,7 +13,26 @@ export default { }, props: { app, + name: { + type: "string", + label: "Segment Name", + description: "The name of the segment", + }, + rule: { + type: "string", + label: "Rule", + description: "SQL-like rule to determine which Contacts belong to this Segment. Help for building a segment rule can be found [here](https://help.elasticemail.com/en/articles/5162182-segment-rules)", + }, }, - async run() { + async run({ $ }) { + const response = await this.app.createSegment({ + $, + data: { + Name: this.name, + Rule: this.rule, + }, + }); + $.export("$summary", "Segment created successfully"); + return response; }, }; diff --git a/components/elastic_email/actions/update-campaign/update-campaign.mjs b/components/elastic_email/actions/update-campaign/update-campaign.mjs index 42c82445d4a69..618df056a966d 100644 --- a/components/elastic_email/actions/update-campaign/update-campaign.mjs +++ b/components/elastic_email/actions/update-campaign/update-campaign.mjs @@ -4,7 +4,7 @@ export default { key: "elastic_email-update-campaign", name: "Update Campaign", description: "Update a campaign in an Elastic Email account. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/campaignsByNamePut)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", annotations: { destructiveHint: true, @@ -13,7 +13,122 @@ export default { }, props: { app, + campaign: { + propDefinition: [ + app, + "campaign", + ], + }, + name: { + type: "string", + label: "Campaign Name", + description: "The name of the campaign", + optional: true, + }, + from: { + type: "string", + label: "From", + description: "Your e-mail with an optional name (e.g.: John Doe email@domain.com)", + optional: true, + }, + recipientListNames: { + propDefinition: [ + app, + "listNames", + ], + label: "Recipient List Names", + description: "Names of lists from your Account to read recipients from", + }, + recipientSegmentNames: { + propDefinition: [ + app, + "segmentNames", + ], + label: "Recipient Segment Names", + description: "Names of segments from your Account to read recipients from", + optional: true, + }, + replyTo: { + type: "string", + label: "Reply To", + description: "To what address should the recipients reply to (e.g. John Doe email@domain.com)", + optional: true, + }, + subject: { + type: "string", + label: "Subject", + description: "Default subject of email", + optional: true, + }, + templateName: { + propDefinition: [ + app, + "templateName", + ], + optional: true, + }, + status: { + propDefinition: [ + app, + "campaignStatus", + ], + optional: true, + }, + excludeRecipientListNames: { + propDefinition: [ + app, + "listNames", + ], + label: "Exclude Recipient List Names", + description: "Names of lists from your Account to exclude from the campaign", + }, + excludeRecipientSegmentNames: { + propDefinition: [ + app, + "segmentNames", + ], + label: "Exclude Recipient Segment Names", + description: "Names of segments from your Account to exclude from the campaign", + optional: true, + }, }, - async run() { + async run({ $ }) { + const campaign = await this.app.getCampaign({ + $, + campaign: this.campaign, + }); + + const response = await this.app.updateCampaign({ + $, + campaign: this.campaign, + data: { + Name: this.name || campaign.Name, + Recipients: { + ListNames: this.recipientListNames || campaign.Recipients.ListNames || undefined, + SegmentNames: this.recipientSegmentNames || campaign.Recipients.SegmentNames || undefined, + }, + Content: [ + { + From: this.from || campaign.Content[0].From, + ReplyTo: this.replyTo || campaign.Content[0].ReplyTo, + Subject: this.subject || campaign.Content[0].Subject, + TemplateName: this.templateName || campaign.Content[0].TemplateName, + }, + ], + Status: this.status || campaign.Status, + ExcludeRecipients: this.excludeRecipientListNames || this.excludeRecipientSegmentNames + ? { + ListNames: this.excludeRecipientListNames + || campaign.ExcludeRecipients.ListNames + || undefined, + SegmentNames: this.excludeRecipientSegmentNames + || campaign.ExcludeRecipients.SegmentNames + || undefined, + } + : undefined, + }, + }); + $.export("$summary", "Campaign updated successfully"); + return response; }, }; diff --git a/components/elastic_email/actions/update-contact/update-contact.mjs b/components/elastic_email/actions/update-contact/update-contact.mjs index 54808a06c320c..f954c3f9a2e21 100644 --- a/components/elastic_email/actions/update-contact/update-contact.mjs +++ b/components/elastic_email/actions/update-contact/update-contact.mjs @@ -1,10 +1,11 @@ import app from "../../elastic_email.app.mjs"; +import { parseObject } from "../../common/utils.mjs"; export default { key: "elastic_email-update-contact", name: "Update Contact", description: "Update a contact in an Elastic Email account. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/contactsByEmailPut)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", annotations: { destructiveHint: true, @@ -13,7 +14,42 @@ export default { }, props: { app, + contact: { + propDefinition: [ + app, + "contact", + ], + }, + firstName: { + type: "string", + label: "First Name", + description: "The first name of the contact", + optional: true, + }, + lastName: { + type: "string", + label: "Last Name", + description: "The last name of the contact", + optional: true, + }, + customFields: { + type: "object", + label: "Custom Fields", + description: "A key-value collection of custom contact fields which can be used in the system. Only already existing custom fields will be saved.", + optional: true, + }, }, - async run() { + async run({ $ }) { + const response = await this.app.updateContact({ + $, + contact: this.contact, + data: { + FirstName: this.firstName, + LastName: this.lastName, + CustomFields: parseObject(this.customFields), + }, + }); + $.export("$summary", "Contact updated successfully"); + return response; }, }; diff --git a/components/elastic_email/elastic_email.app.mjs b/components/elastic_email/elastic_email.app.mjs index a5d4d3c76b395..74d97c66189e2 100644 --- a/components/elastic_email/elastic_email.app.mjs +++ b/components/elastic_email/elastic_email.app.mjs @@ -52,7 +52,7 @@ export default { label: "Campaign", description: "The name of a campaign", async options({ page }) { - const { campaigns } = await this.listCampaigns({ + const campaigns = await this.listCampaigns({ params: { limit: LIMIT, offset: LIMIT * page, @@ -66,7 +66,7 @@ export default { label: "Contact", description: "The email address of a contact", async options({ page }) { - const { contacts } = await this.listContacts({ + const contacts = await this.listContacts({ params: { limit: LIMIT, offset: LIMIT * page, @@ -80,7 +80,7 @@ export default { label: "Segment Names", description: "The name of a segment", async options({ page }) { - const { segments } = await this.listSegments({ + const segments = await this.listSegments({ params: { limit: LIMIT, offset: LIMIT * page, @@ -103,6 +103,22 @@ export default { "Cancelled", "Draft", ], + }, + contactStatus: { + type: "string", + label: "Contact Status", + description: "The status of a contact", + options: [ + "Transactional", + "Engaged", + "Active", + "Bounced", + "Unsubscribed", + "Abuse", + "Inactive", + "Stale", + "NotConfirmed", + ], optional: true, }, }, @@ -124,6 +140,14 @@ export default { ...opts, }); }, + getCampaign({ + campaign, ...opts + }) { + return this._makeRequest({ + path: `/campaigns/${campaign}`, + ...opts, + }); + }, loadEvents(opts = {}) { return this._makeRequest({ path: "/events", diff --git a/components/elastic_email/sources/new-delivery-event/new-delivery-event.mjs b/components/elastic_email/sources/new-delivery-event/new-delivery-event.mjs index e69de29bb2d1d..c70af2f314bd6 100644 --- a/components/elastic_email/sources/new-delivery-event/new-delivery-event.mjs +++ b/components/elastic_email/sources/new-delivery-event/new-delivery-event.mjs @@ -0,0 +1,44 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "elastic_email-new-delivery-event", + name: "New Delivery Event", + description: "Emit new event when a delivery event occurs. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/eventsGet).", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + eventTypes: { + type: "string[]", + label: "Event Types", + description: "The type of events to listen for", + options: [ + "Submission", + "FailedAttempt", + "Bounce", + "Sent", + "Open", + "Click", + "Unsubscribe", + "Complaint", + ], + }, + }, + methods: { + ...common.methods, + getEventType() { + return this.eventTypes; + }, + getDateField() { + return "EventDate"; + }, + getIdField() { + return "MsgID"; + }, + getSummary() { + return "New delivery event"; + }, + }, +}; From 0784860622b466fa8045823b2e5a6f458abd2041 Mon Sep 17 00:00:00 2001 From: Michelle Bergeron Date: Mon, 3 Nov 2025 15:20:28 -0500 Subject: [PATCH 3/5] pnpm-lock.yaml --- pnpm-lock.yaml | 43 +++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7568a70702ea..83eb5ab91a97f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3414,8 +3414,7 @@ importers: components/cronly: {} - components/cronlytic: - specifiers: {} + components/cronlytic: {} components/crossmint: {} @@ -3624,8 +3623,7 @@ importers: components/data_police_uk: {} - components/data_soap: - specifiers: {} + components/data_soap: {} components/data_stores: dependencies: @@ -3733,13 +3731,11 @@ importers: specifier: ^1.5.1 version: 1.6.6 - components/decodo: - specifiers: {} + components/decodo: {} components/deel: {} - components/deep_tagger: - specifiers: {} + components/deep_tagger: {} components/deepgram: dependencies: @@ -3834,8 +3830,7 @@ importers: specifier: ^0.3.2 version: 0.3.2 - components/deutschlandgpt: - specifiers: {} + components/deutschlandgpt: {} components/dev_to: dependencies: @@ -4472,8 +4467,7 @@ importers: components/ebay: {} - components/echowin: - specifiers: {} + components/echowin: {} components/echtpost_postcards: dependencies: @@ -4532,8 +4526,8 @@ importers: components/elastic_email: dependencies: '@pipedream/platform': - specifier: ^3.0.3 - version: 3.0.3 + specifier: ^3.1.0 + version: 3.1.0 components/elasticemail: {} @@ -4794,8 +4788,7 @@ importers: specifier: ^1.1.1 version: 1.6.6 - components/evervault: - specifiers: {} + components/evervault: {} components/everwebinar: dependencies: @@ -4862,8 +4855,7 @@ importers: components/extracta_ai: {} - components/extruct_ai: - specifiers: {} + components/extruct_ai: {} components/eyepop_ai: {} @@ -4977,8 +4969,7 @@ importers: specifier: ^3.0.3 version: 3.0.3 - components/featherless: - specifiers: {} + components/featherless: {} components/feathery: dependencies: @@ -5047,8 +5038,7 @@ importers: specifier: ^3.0.0 version: 3.0.3 - components/filescan: - specifiers: {} + components/filescan: {} components/filestack: dependencies: @@ -5986,8 +5976,7 @@ importers: components/google_chat_developer_app: {} - components/google_chat_service_account_key: - specifiers: {} + components/google_chat_service_account_key: {} components/google_classroom: dependencies: @@ -7367,8 +7356,7 @@ importers: specifier: ^1.1.1 version: 1.6.6 - components/ipregistry: - specifiers: {} + components/ipregistry: {} components/ipstack: dependencies: @@ -8222,8 +8210,7 @@ importers: specifier: ^1.0.3 version: 1.0.3 - components/linkupapi: - specifiers: {} + components/linkupapi: {} components/linode: dependencies: From 016d67bc11efff4d0d64cd1e4ac2ca6f58110edd Mon Sep 17 00:00:00 2001 From: Michelle Bergeron Date: Mon, 3 Nov 2025 15:24:49 -0500 Subject: [PATCH 4/5] versions --- .../sources/new-contact-added/new-contact-added.mjs | 2 +- .../elastic_email/sources/new-email-click/new-email-click.mjs | 2 +- .../elastic_email/sources/new-email-open/new-email-open.mjs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/elastic_email/sources/new-contact-added/new-contact-added.mjs b/components/elastic_email/sources/new-contact-added/new-contact-added.mjs index 0037da2861785..2d55b5dc03222 100644 --- a/components/elastic_email/sources/new-contact-added/new-contact-added.mjs +++ b/components/elastic_email/sources/new-contact-added/new-contact-added.mjs @@ -6,7 +6,7 @@ export default { key: "elastic_email-new-contact-added", name: "New Contact Added", description: "Emit new event when a new contact is added to a mailing list. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/contactsGet)", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/elastic_email/sources/new-email-click/new-email-click.mjs b/components/elastic_email/sources/new-email-click/new-email-click.mjs index d92d51ea2ef27..cb139bc005de3 100644 --- a/components/elastic_email/sources/new-email-click/new-email-click.mjs +++ b/components/elastic_email/sources/new-email-click/new-email-click.mjs @@ -6,7 +6,7 @@ export default { key: "elastic_email-new-email-click", name: "New Email Click", description: "Emit new event when a recipient clicks a link in an email. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/eventsGet).", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/elastic_email/sources/new-email-open/new-email-open.mjs b/components/elastic_email/sources/new-email-open/new-email-open.mjs index add9485056fae..932475129380a 100644 --- a/components/elastic_email/sources/new-email-open/new-email-open.mjs +++ b/components/elastic_email/sources/new-email-open/new-email-open.mjs @@ -6,7 +6,7 @@ export default { key: "elastic_email-new-email-open", name: "New Email Open", description: "Emit new event when a recipient opens an email. [See the documentation](https://elasticemail.com/developers/api-documentation/rest-api#operation/eventsGet).", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", methods: { From 86dc611ab1e4eaf2eec5521c64416d4d540c3e12 Mon Sep 17 00:00:00 2001 From: Michelle Bergeron Date: Tue, 4 Nov 2025 11:35:09 -0500 Subject: [PATCH 5/5] improve description and validate from & replyTo props --- .../actions/create-campaign/create-campaign.mjs | 12 ++++++++++-- .../actions/send-email/send-email.mjs | 16 +++++++++++++--- .../actions/update-campaign/update-campaign.mjs | 13 +++++++++++-- components/elastic_email/common/utils.mjs | 12 +++++++++++- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/components/elastic_email/actions/create-campaign/create-campaign.mjs b/components/elastic_email/actions/create-campaign/create-campaign.mjs index cbd5ab40c0cbd..e6f2bf03def03 100644 --- a/components/elastic_email/actions/create-campaign/create-campaign.mjs +++ b/components/elastic_email/actions/create-campaign/create-campaign.mjs @@ -1,5 +1,6 @@ import app from "../../elastic_email.app.mjs"; import { ConfigurationError } from "@pipedream/platform"; +import { isValidEmailFormat } from "../../common/utils.mjs"; export default { key: "elastic_email-create-campaign", @@ -22,7 +23,7 @@ export default { from: { type: "string", label: "From", - description: "Your e-mail with an optional name (e.g.: John Doe email@domain.com)", + description: "Your e-mail with an optional name (e.g.: `email@domain.com` or `John Doe `)", }, recipientListNames: { propDefinition: [ @@ -44,7 +45,7 @@ export default { replyTo: { type: "string", label: "Reply To", - description: "To what address should the recipients reply to (e.g. John Doe email@domain.com)", + description: "To what address should the recipients reply to (e.g. `email@domain.com` or `John Doe `)", }, status: { propDefinition: [ @@ -84,6 +85,13 @@ export default { }, }, async run({ $ }) { + if (this.from && !isValidEmailFormat(this.from)) { + throw new ConfigurationError("Invalid email format for 'From'"); + } + if (this.replyTo && !isValidEmailFormat(this.replyTo)) { + throw new ConfigurationError("Invalid email format for 'Reply To'"); + } + if (!this.recipientListNames && !this.recipientSegmentNames) { throw new ConfigurationError("You must provide at least one list or segment to read recipients from"); } diff --git a/components/elastic_email/actions/send-email/send-email.mjs b/components/elastic_email/actions/send-email/send-email.mjs index aaecbc71451f8..dcf31787cacad 100644 --- a/components/elastic_email/actions/send-email/send-email.mjs +++ b/components/elastic_email/actions/send-email/send-email.mjs @@ -2,7 +2,10 @@ import { BODY_CONTENT_TYPE_OPTIONS, ENCODING_OPTIONS, } from "../../common/constants.mjs"; -import { parseObject } from "../../common/utils.mjs"; +import { + parseObject, isValidEmailFormat, +} from "../../common/utils.mjs"; +import { ConfigurationError } from "@pipedream/platform"; import app from "../../elastic_email.app.mjs"; export default { @@ -26,7 +29,7 @@ export default { from: { type: "string", label: "From", - description: "Your e-mail with an optional name (e.g.: email@domain.com)", + description: "Your e-mail with an optional name (e.g.: `email@domain.com` or `John Doe `)", }, bodyContentType: { type: "string", @@ -50,7 +53,7 @@ export default { replyTo: { type: "string", label: "Reply To", - description: "To what address should the recipients reply to (e.g. email@domain.com)", + description: "To what address should the recipients reply to (e.g. `email@domain.com` or `John Doe `)", optional: true, }, subject: { @@ -105,6 +108,13 @@ export default { }, }, async run({ $ }) { + if (this.from && !isValidEmailFormat(this.from)) { + throw new ConfigurationError("Invalid email format for 'From'"); + } + if (this.replyTo && !isValidEmailFormat(this.replyTo)) { + throw new ConfigurationError("Invalid email format for 'Reply To'"); + } + const response = await this.app.sendBulkEmails({ $, data: { diff --git a/components/elastic_email/actions/update-campaign/update-campaign.mjs b/components/elastic_email/actions/update-campaign/update-campaign.mjs index 618df056a966d..9a05251eb1fdb 100644 --- a/components/elastic_email/actions/update-campaign/update-campaign.mjs +++ b/components/elastic_email/actions/update-campaign/update-campaign.mjs @@ -1,4 +1,6 @@ import app from "../../elastic_email.app.mjs"; +import { isValidEmailFormat } from "../../common/utils.mjs"; +import { ConfigurationError } from "@pipedream/platform"; export default { key: "elastic_email-update-campaign", @@ -28,7 +30,7 @@ export default { from: { type: "string", label: "From", - description: "Your e-mail with an optional name (e.g.: John Doe email@domain.com)", + description: "Your e-mail with an optional name (e.g.: `email@domain.com` or `John Doe `)", optional: true, }, recipientListNames: { @@ -51,7 +53,7 @@ export default { replyTo: { type: "string", label: "Reply To", - description: "To what address should the recipients reply to (e.g. John Doe email@domain.com)", + description: "To what address should the recipients reply to (e.g. `email@domain.com` or `John Doe `)", optional: true, }, subject: { @@ -93,6 +95,13 @@ export default { }, }, async run({ $ }) { + if (this.from && !isValidEmailFormat(this.from)) { + throw new ConfigurationError("Invalid email format for 'From'"); + } + if (this.replyTo && !isValidEmailFormat(this.replyTo)) { + throw new ConfigurationError("Invalid email format for 'Reply To'"); + } + const campaign = await this.app.getCampaign({ $, campaign: this.campaign, diff --git a/components/elastic_email/common/utils.mjs b/components/elastic_email/common/utils.mjs index dcc9cc61f6f41..9444199720e09 100644 --- a/components/elastic_email/common/utils.mjs +++ b/components/elastic_email/common/utils.mjs @@ -1,4 +1,4 @@ -export const parseObject = (obj) => { +const parseObject = (obj) => { if (!obj) return undefined; if (Array.isArray(obj)) { @@ -22,3 +22,13 @@ export const parseObject = (obj) => { } return obj; }; + +const isValidEmailFormat = (str) => { + const emailPattern = /^(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}|[^<>]+<\s*[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\s*>)$/; + return emailPattern.test(str.trim()); +}; + +export { + parseObject, + isValidEmailFormat, +};