Skip to content

Commit 9b59278

Browse files
authored
feat(CE): Added Mailchimp Destination Connector (#437)
* feat(CE): Added Mailchimp Destination Connector
1 parent 93e8767 commit 9b59278

File tree

11 files changed

+526
-1
lines changed

11 files changed

+526
-1
lines changed

integrations/Gemfile

+2
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ gem "google-cloud-ai_platform-v1"
7777

7878
gem "tiny_tds"
7979

80+
gem "MailchimpMarketing"
81+
8082
group :development, :test do
8183
gem "simplecov", require: false
8284
gem "simplecov_json_formatter", require: false

integrations/Gemfile.lock

+5
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ PATH
4444
GEM
4545
remote: https://rubygems.org/
4646
specs:
47+
MailchimpMarketing (3.0.80)
48+
excon (>= 0.76.0, < 1)
49+
json (~> 2.1, >= 2.1.0)
4750
activesupport (7.1.3.3)
4851
base64
4952
bigdecimal
@@ -151,6 +154,7 @@ GEM
151154
bigdecimal (>= 3.1.4)
152155
ethon (0.16.0)
153156
ffi (>= 1.15.0)
157+
excon (0.112.0)
154158
faraday (2.8.1)
155159
base64
156160
faraday-net_http (>= 2.0, < 3.1)
@@ -407,6 +411,7 @@ PLATFORMS
407411
x86_64-linux
408412

409413
DEPENDENCIES
414+
MailchimpMarketing
410415
activesupport
411416
async-websocket (~> 0.8.0)
412417
aws-sdk-athena

integrations/lib/multiwoven/integrations.rb

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
require "aws-sdk-sagemakerruntime"
3737
require "google/cloud/ai_platform/v1"
3838
require "grpc"
39+
require "MailchimpMarketing"
3940

4041
# Service
4142
require_relative "integrations/config"
@@ -90,6 +91,7 @@
9091
require_relative "integrations/destination/oracle_db/client"
9192
require_relative "integrations/destination/microsoft_excel/client"
9293
require_relative "integrations/destination/microsoft_sql/client"
94+
require_relative "integrations/destination/mailchimp/client"
9395

9496
module Multiwoven
9597
module Integrations
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# frozen_string_literal: true
2+
3+
module Multiwoven
4+
module Integrations
5+
module Destination
6+
module Mailchimp
7+
include Multiwoven::Integrations::Core
8+
9+
API_VERSION = "3.0"
10+
11+
class Client < DestinationConnector
12+
prepend Multiwoven::Integrations::Core::RateLimiter
13+
14+
def check_connection(connection_config)
15+
connection_config = connection_config.with_indifferent_access
16+
initialize_client(connection_config)
17+
authenticate_client
18+
success_status
19+
rescue StandardError => e
20+
failure_status(e)
21+
end
22+
23+
def discover(_connection_config = nil)
24+
catalog = build_catalog(load_catalog)
25+
catalog.to_multiwoven_message
26+
rescue StandardError => e
27+
handle_exception(e, {
28+
context: "MAILCHIMP:DISCOVER:EXCEPTION",
29+
type: "error"
30+
})
31+
end
32+
33+
def write(sync_config, records, _action = "create")
34+
@sync_config = sync_config
35+
initialize_client(sync_config.destination.connection_specification)
36+
process_records(records, sync_config.stream)
37+
rescue StandardError => e
38+
handle_exception(e, {
39+
context: "MAILCHIMP:WRITE:EXCEPTION",
40+
type: "error",
41+
sync_id: @sync_config.sync_id,
42+
sync_run_id: @sync_config.sync_run_id
43+
})
44+
end
45+
46+
private
47+
48+
def initialize_client(config)
49+
config = config.with_indifferent_access
50+
@client = MailchimpMarketing::Client.new
51+
@client.set_config({
52+
api_key: config[:api_key],
53+
server: config[:api_key].split("-").last
54+
})
55+
@list_id = config[:list_id]
56+
@email_template_id = config[:email_template_id] || ""
57+
end
58+
59+
def process_records(records, stream)
60+
log_message_array = []
61+
write_success = 0
62+
write_failure = 0
63+
properties = stream.json_schema[:properties]
64+
65+
records.each do |record_object|
66+
record = extract_data(record_object, properties)
67+
args = [stream.name, "Id", record]
68+
begin
69+
response = send_to_mailchimp(record, stream.name)
70+
write_success += 1
71+
log_message_array << log_request_response("info", args, response)
72+
rescue StandardError => e
73+
handle_exception(e, {
74+
context: "MAILCHIMP:WRITE:EXCEPTION",
75+
type: "error",
76+
sync_id: @sync_config.sync_id,
77+
sync_run_id: @sync_config.sync_run_id
78+
})
79+
write_failure += 1
80+
log_message_array << log_request_response("error", args, e.message)
81+
end
82+
end
83+
tracking_message(write_success, write_failure, log_message_array)
84+
end
85+
86+
def send_to_mailchimp(record, stream_name)
87+
case stream_name
88+
when "Audience"
89+
@client.lists.set_list_member(@list_id, Digest::MD5.hexdigest(record[:email].downcase), {
90+
email_address: record[:email],
91+
status_if_new: "subscribed",
92+
merge_fields: {
93+
FNAME: record[:first_name],
94+
LNAME: record[:last_name]
95+
}
96+
})
97+
when "Tags"
98+
@client.lists.update_list_member_tags(@list_id, Digest::MD5.hexdigest(record[:email].downcase), {
99+
tags: record[:tags].map { |tag| { name: tag, status: "active" } }
100+
})
101+
when "Campaigns"
102+
campaign = @client.campaigns.create({
103+
type: "regular",
104+
recipients: { list_id: @list_id },
105+
settings: {
106+
subject_line: record[:subject],
107+
from_name: record[:from_name],
108+
reply_to: record[:reply_to]
109+
}
110+
})
111+
if @email_template_id
112+
@client.campaigns.set_content(campaign["id"], {
113+
template: { id: @email_template_id }
114+
})
115+
else
116+
@client.campaigns.set_content(campaign["id"], {
117+
plain_text: record[:content]
118+
})
119+
end
120+
@client.campaigns.send(campaign["id"])
121+
else
122+
raise "Unsupported stream type: #{stream_name}"
123+
end
124+
end
125+
126+
def authenticate_client
127+
@client.lists.get_all_lists
128+
end
129+
130+
def load_catalog
131+
read_json(CATALOG_SPEC_PATH)
132+
end
133+
134+
def log_debug(message)
135+
Multiwoven::Integrations::Service.logger.debug(message)
136+
end
137+
end
138+
end
139+
end
140+
end
141+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
{
2+
"request_rate_limit": 100000,
3+
"request_rate_limit_unit": "day",
4+
"request_rate_concurrency": 10,
5+
"streams": [
6+
{
7+
"name": "Audience",
8+
"action": "create",
9+
"json_schema": {
10+
"type": "object",
11+
"additionalProperties": true,
12+
"required": ["email"],
13+
"properties": {
14+
"email": {
15+
"type": "string",
16+
"format": "email"
17+
},
18+
"first_name": {
19+
"type": "string"
20+
},
21+
"last_name": {
22+
"type": "string"
23+
},
24+
"status": {
25+
"type": "string",
26+
"enum": ["subscribed", "unsubscribed", "cleaned", "pending"]
27+
},
28+
"tags": {
29+
"type": "array",
30+
"items": {
31+
"type": "string"
32+
}
33+
},
34+
"merge_fields": {
35+
"type": "object",
36+
"additionalProperties": true,
37+
"properties": {
38+
"FNAME": {
39+
"type": "string"
40+
},
41+
"LNAME": {
42+
"type": "string"
43+
}
44+
}
45+
},
46+
"language": {
47+
"type": "string"
48+
},
49+
"vip": {
50+
"type": "boolean"
51+
},
52+
"timestamp_signup": {
53+
"type": "string",
54+
"format": "date-time"
55+
},
56+
"ip_signup": {
57+
"type": "string",
58+
"format": "ipv4"
59+
},
60+
"timestamp_opt": {
61+
"type": "string",
62+
"format": "date-time"
63+
},
64+
"ip_opt": {
65+
"type": "string",
66+
"format": "ipv4"
67+
}
68+
}
69+
},
70+
"supported_sync_modes": ["incremental"],
71+
"source_defined_cursor": true,
72+
"default_cursor_field": ["timestamp_opt"],
73+
"source_defined_primary_key": [["email"]]
74+
},
75+
{
76+
"name": "Tags",
77+
"action": "create",
78+
"json_schema": {
79+
"type": "object",
80+
"additionalProperties": true,
81+
"required": ["email", "tags"],
82+
"properties": {
83+
"email": {
84+
"type": "string",
85+
"format": "email"
86+
},
87+
"tags": {
88+
"type": "array",
89+
"items": {
90+
"type": "string"
91+
}
92+
}
93+
}
94+
},
95+
"supported_sync_modes": ["incremental"],
96+
"source_defined_cursor": true,
97+
"default_cursor_field": ["updated"]
98+
},
99+
{
100+
"name": "Campaigns",
101+
"action": "create",
102+
"json_schema": {
103+
"type": "object",
104+
"additionalProperties": true,
105+
"required": ["subject", "from_name", "reply_to", "recipients"],
106+
"properties": {
107+
"subject": {
108+
"type": "string"
109+
},
110+
"from_name": {
111+
"type": "string"
112+
},
113+
"reply_to": {
114+
"type": "string",
115+
"format": "email"
116+
},
117+
"recipients": {
118+
"type": "object",
119+
"properties": {
120+
"list_id": {
121+
"type": "string"
122+
}
123+
}
124+
},
125+
"template_id": {
126+
"type": "string"
127+
},
128+
"content": {
129+
"type": "string"
130+
},
131+
"send_time": {
132+
"type": "string",
133+
"format": "date-time"
134+
}
135+
}
136+
},
137+
"supported_sync_modes": ["full_refresh"],
138+
"source_defined_cursor": false
139+
}
140+
]
141+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"data": {
3+
"name": "Mailchimp",
4+
"title": "Mailchimp",
5+
"connector_type": "destination",
6+
"category": "Marketing Automation",
7+
"documentation_url": "https://docs.multiwoven.com/destinations/crm/mailchimp",
8+
"github_issue_label": "destination-mailchimp",
9+
"icon": "icon.svg",
10+
"license": "MIT",
11+
"release_stage": "alpha",
12+
"support_level": "community",
13+
"tags": ["language:ruby", "multiwoven"]
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"documentation_url": "https://docs.multiwoven.com/integrations/destination/mailchimp",
3+
"stream_type": "static",
4+
"connection_specification": {
5+
"$schema": "http://json-schema.org/draft-07/schema#",
6+
"title": "Mailchimp",
7+
"type": "object",
8+
"required": ["api_key", "list_id"],
9+
"properties": {
10+
"api_key": {
11+
"type": "string",
12+
"multiwoven_secret": true,
13+
"title": "API Key",
14+
"order": 0
15+
},
16+
"list_id": {
17+
"type": "string",
18+
"title": "List Id",
19+
"order": 1
20+
},
21+
"email_template_id": {
22+
"type": "string",
23+
"title": "Email Template Id",
24+
"order": 2
25+
}
26+
}
27+
}
28+
}
Loading

0 commit comments

Comments
 (0)