sendara 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie"
4
+
5
+ module Sendara
6
+ class Configuration
7
+ attr_accessor :api_key, :base_url, :timeout, :max_retries
8
+
9
+ def initialize
10
+ @api_key = ENV["SENDARA_API_KEY"]
11
+ @base_url = ENV["SENDARA_BASE_URL"]
12
+ @timeout = nil
13
+ @max_retries = nil
14
+ end
15
+
16
+ def client_options
17
+ options = {}
18
+ options[:base_url] = base_url unless base_url.nil? || base_url.to_s.empty?
19
+ options[:timeout] = timeout unless timeout.nil?
20
+ options[:max_retries] = max_retries unless max_retries.nil?
21
+ options
22
+ end
23
+ end
24
+
25
+ class << self
26
+ def configuration
27
+ @configuration ||= Configuration.new
28
+ end
29
+
30
+ def configure
31
+ yield(configuration) if block_given?
32
+ reset_client!
33
+ configuration
34
+ end
35
+
36
+ def client
37
+ @client ||= build_client
38
+ end
39
+
40
+ def reset_client!
41
+ @client = nil
42
+ end
43
+
44
+ private
45
+
46
+ def build_client
47
+ key = configuration.api_key
48
+ raise Error, "Sendara API key is missing. Set ENV[\"SENDARA_API_KEY\"] or config.sendara.api_key." if key.nil? || key.to_s.empty?
49
+
50
+ Client.new(key, **configuration.client_options)
51
+ end
52
+ end
53
+
54
+ class Railtie < ::Rails::Railtie
55
+ config.sendara = ActiveSupport::OrderedOptions.new
56
+
57
+ initializer "sendara.configure" do |app|
58
+ options = app.config.sendara
59
+
60
+ Sendara.configure do |sendara_config|
61
+ sendara_config.api_key = options.api_key unless options.api_key.nil?
62
+ sendara_config.base_url = options.base_url unless options.base_url.nil?
63
+ sendara_config.timeout = options.timeout unless options.timeout.nil?
64
+ sendara_config.max_retries = options.max_retries unless options.max_retries.nil?
65
+ end
66
+ end
67
+
68
+ generators do
69
+ require_relative "generators/install_generator"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendara
4
+ class Resource
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ protected
10
+
11
+ attr_reader :client
12
+
13
+ def request(method, path, body: nil, query: {})
14
+ client.request(method, path, body: body, query: query)
15
+ end
16
+
17
+ def compact_params(hash)
18
+ hash.reject { |_key, value| value.nil? }
19
+ end
20
+
21
+ def encode(segment)
22
+ require "erb"
23
+ ERB::Util.url_encode(segment.to_s)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendara
4
+ module Resources
5
+ class ApiKeys < Resource
6
+ def list
7
+ response = request(:get, "/v1/keys") || {}
8
+ keys = response["keys"]
9
+ keys.is_a?(Array) ? keys : []
10
+ end
11
+
12
+ def create(scope: nil, test_mode: nil)
13
+ body = compact_params("scope" => scope, "test_mode" => test_mode)
14
+ request(:post, "/v1/keys", body: body) || {}
15
+ end
16
+
17
+ def rotate(id)
18
+ response = request(:post, "/v1/keys/#{encode(id)}/rotate") || {}
19
+ response["key"].to_s
20
+ end
21
+
22
+ def revoke(id)
23
+ request(:delete, "/v1/keys/#{encode(id)}")
24
+ nil
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendara
4
+ module Resources
5
+ class Billing < Resource
6
+ def get
7
+ request(:get, "/v1/billing") || {}
8
+ end
9
+
10
+ def checkout(plan: nil, period: nil)
11
+ body = compact_params("plan" => plan, "period" => period)
12
+ response = request(:post, "/v1/billing/checkout", body: body) || {}
13
+ response["url"].to_s
14
+ end
15
+
16
+ def portal
17
+ response = request(:post, "/v1/billing/portal") || {}
18
+ response["url"].to_s
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendara
4
+ module Resources
5
+ class Broadcasts < Resource
6
+ def list(limit: nil, offset: nil)
7
+ query = compact_params("limit" => limit, "offset" => offset)
8
+ response = request(:get, "/v1/broadcasts", query: query) || {}
9
+ broadcasts = response["broadcasts"]
10
+ broadcasts.is_a?(Array) ? broadcasts : []
11
+ end
12
+
13
+ def get(id)
14
+ request(:get, "/v1/broadcasts/#{encode(id)}") || {}
15
+ end
16
+
17
+ def create(from_email:, name: nil, subject: nil, body_html: nil, body_text: nil,
18
+ template_id: nil, message_type: nil, audience_list_id: nil,
19
+ recipients: nil, scheduled_at: nil, send_now: nil)
20
+ request(:post, "/v1/broadcasts", body: broadcast_body(
21
+ from_email: from_email, name: name, subject: subject, body_html: body_html,
22
+ body_text: body_text, template_id: template_id, message_type: message_type,
23
+ audience_list_id: audience_list_id, recipients: recipients,
24
+ scheduled_at: scheduled_at, send_now: send_now
25
+ )) || {}
26
+ end
27
+
28
+ def send(id)
29
+ request(:post, "/v1/broadcasts/#{encode(id)}/send") || {}
30
+ end
31
+
32
+ def cancel(id)
33
+ request(:post, "/v1/broadcasts/#{encode(id)}/cancel") || {}
34
+ end
35
+
36
+ def delete(id)
37
+ request(:delete, "/v1/broadcasts/#{encode(id)}")
38
+ nil
39
+ end
40
+
41
+ def bulk_send(from_email:, name: nil, subject: nil, body_html: nil, body_text: nil,
42
+ template_id: nil, message_type: nil, audience_list_id: nil,
43
+ recipients: nil, scheduled_at: nil, send_now: nil)
44
+ request(:post, "/v1/send/bulk", body: broadcast_body(
45
+ from_email: from_email, name: name, subject: subject, body_html: body_html,
46
+ body_text: body_text, template_id: template_id, message_type: message_type,
47
+ audience_list_id: audience_list_id, recipients: recipients,
48
+ scheduled_at: scheduled_at, send_now: send_now
49
+ )) || {}
50
+ end
51
+
52
+ private
53
+
54
+ def broadcast_body(from_email:, name:, subject:, body_html:, body_text:, template_id:,
55
+ message_type:, audience_list_id:, recipients:, scheduled_at:, send_now:)
56
+ compact_params(
57
+ "name" => name,
58
+ "from_email" => from_email,
59
+ "subject" => subject,
60
+ "body_html" => body_html,
61
+ "body_text" => body_text,
62
+ "template_id" => template_id,
63
+ "message_type" => message_type,
64
+ "audience_list_id" => audience_list_id,
65
+ "recipients" => recipients,
66
+ "scheduled_at" => scheduled_at,
67
+ "send_now" => send_now
68
+ )
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendara
4
+ module Resources
5
+ class Contacts < Resource
6
+ def lists
7
+ @lists ||= Lists.new(client)
8
+ end
9
+
10
+ def list(limit: nil, offset: nil)
11
+ query = compact_params("limit" => limit, "offset" => offset)
12
+ response = request(:get, "/v1/contacts", query: query) || {}
13
+ contacts = response["contacts"]
14
+ contacts.is_a?(Array) ? contacts : []
15
+ end
16
+
17
+ def get(id)
18
+ request(:get, "/v1/contacts/#{encode(id)}") || {}
19
+ end
20
+
21
+ def create(email: nil, phone_number: nil, device_token: nil, first_name: nil,
22
+ last_name: nil, attributes: nil, tags: nil, email_consent: nil,
23
+ sms_consent: nil, push_consent: nil, voice_consent: nil)
24
+ body = contact_body(
25
+ email: email, phone_number: phone_number, device_token: device_token,
26
+ first_name: first_name, last_name: last_name, attributes: attributes,
27
+ tags: tags, email_consent: email_consent, sms_consent: sms_consent,
28
+ push_consent: push_consent, voice_consent: voice_consent
29
+ )
30
+ request(:post, "/v1/contacts", body: body) || {}
31
+ end
32
+
33
+ def update(id, email: nil, phone_number: nil, device_token: nil, first_name: nil,
34
+ last_name: nil, attributes: nil, tags: nil, email_consent: nil,
35
+ sms_consent: nil, push_consent: nil, voice_consent: nil)
36
+ body = contact_body(
37
+ email: email, phone_number: phone_number, device_token: device_token,
38
+ first_name: first_name, last_name: last_name, attributes: attributes,
39
+ tags: tags, email_consent: email_consent, sms_consent: sms_consent,
40
+ push_consent: push_consent, voice_consent: voice_consent
41
+ )
42
+ request(:put, "/v1/contacts/#{encode(id)}", body: body) || {}
43
+ end
44
+
45
+ def delete(id)
46
+ request(:delete, "/v1/contacts/#{encode(id)}")
47
+ nil
48
+ end
49
+
50
+ def lists_for(id)
51
+ response = request(:get, "/v1/contacts/#{encode(id)}/lists") || {}
52
+ lists = response["lists"]
53
+ lists.is_a?(Array) ? lists : []
54
+ end
55
+
56
+ def activity(id, limit: nil)
57
+ query = compact_params("limit" => limit)
58
+ response = request(:get, "/v1/contacts/#{encode(id)}/activity", query: query) || {}
59
+ events = response["events"]
60
+ events.is_a?(Array) ? events : []
61
+ end
62
+
63
+ def import(s3_key:, format:)
64
+ body = { "s3_key" => s3_key, "format" => format }
65
+ request(:post, "/v1/contacts/import", body: body) || {}
66
+ end
67
+
68
+ def import_inline(rows:, list_id: nil, dry_run: nil)
69
+ body = compact_params(
70
+ "rows" => rows,
71
+ "list_id" => list_id,
72
+ "dry_run" => dry_run
73
+ )
74
+ request(:post, "/v1/contacts/import/inline", body: body) || {}
75
+ end
76
+
77
+ def bulk(ids:, action:, tag: nil, list_id: nil, channel: nil, consent: nil)
78
+ body = compact_params(
79
+ "ids" => ids,
80
+ "action" => action,
81
+ "tag" => tag,
82
+ "list_id" => list_id,
83
+ "channel" => channel,
84
+ "consent" => consent
85
+ )
86
+ response = request(:post, "/v1/contacts/bulk", body: body) || {}
87
+ (response["affected"] || 0).to_i
88
+ end
89
+
90
+ def segments_preview(segment_rules)
91
+ response = request(:post, "/v1/contacts/segments/preview",
92
+ body: { "segment_rules" => segment_rules }) || {}
93
+ (response["count"] || 0).to_i
94
+ end
95
+
96
+ def usage
97
+ request(:get, "/v1/contacts/usage") || {}
98
+ end
99
+
100
+ private
101
+
102
+ def contact_body(email:, phone_number:, device_token:, first_name:, last_name:,
103
+ attributes:, tags:, email_consent:, sms_consent:, push_consent:,
104
+ voice_consent:)
105
+ compact_params(
106
+ "email" => email,
107
+ "phone_number" => phone_number,
108
+ "device_token" => device_token,
109
+ "first_name" => first_name,
110
+ "last_name" => last_name,
111
+ "attributes" => attributes,
112
+ "tags" => tags,
113
+ "email_consent" => email_consent,
114
+ "sms_consent" => sms_consent,
115
+ "push_consent" => push_consent,
116
+ "voice_consent" => voice_consent
117
+ )
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module Sendara
6
+ module Resources
7
+ class Domains < Resource
8
+ def list
9
+ response = request(:get, "/v1/domains") || {}
10
+ domains = response["domains"]
11
+ domains.is_a?(Array) ? domains : []
12
+ end
13
+
14
+ def create(domain)
15
+ request(:post, "/v1/domains", body: { "domain" => domain }) || {}
16
+ end
17
+
18
+ def get(domain)
19
+ request(:get, "/v1/domains/#{encode(domain)}") || {}
20
+ end
21
+
22
+ def verify(domain)
23
+ request(:post, "/v1/domains/#{encode(domain)}/verify") || {}
24
+ end
25
+
26
+ def get_bimi(domain)
27
+ request(:get, "/v1/domains/#{encode(domain)}/bimi") || {}
28
+ end
29
+
30
+ def set_bimi(domain, logo_url)
31
+ request(:put, "/v1/domains/#{encode(domain)}/bimi", body: { "logo_url" => logo_url }) || {}
32
+ end
33
+
34
+ def upload_bimi_logo(domain, svg, filename: "logo.svg")
35
+ body = {
36
+ "filename" => filename,
37
+ "content_type" => "image/svg+xml",
38
+ "data" => Base64.strict_encode64(svg)
39
+ }
40
+ request(:post, "/v1/domains/#{encode(domain)}/bimi/logo", body: body) || {}
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendara
4
+ module Resources
5
+ class Emails < Resource
6
+ def send(to:, from: nil, subject: nil, html: nil, text: nil, message_type: nil,
7
+ template_id: nil, template_vars: nil, metadata: nil, store_payload: nil,
8
+ test_send: nil, idempotency_key: nil)
9
+ meta = metadata.nil? ? {} : metadata.dup
10
+ meta["from_email"] = from unless from.nil?
11
+
12
+ body = {
13
+ "channel" => "email",
14
+ "idempotency_key" => idempotency_key || Client.generate_idempotency_key,
15
+ "destination" => { "email" => to },
16
+ "payload" => {
17
+ "subject" => subject,
18
+ "body_html" => html,
19
+ "body_text" => text
20
+ },
21
+ "metadata" => meta
22
+ }
23
+
24
+ body["message_type"] = message_type unless message_type.nil?
25
+ body["template_id"] = template_id unless template_id.nil?
26
+ body["template_vars"] = template_vars unless template_vars.nil?
27
+ body["store_payload"] = store_payload unless store_payload.nil?
28
+ body["test_send"] = test_send unless test_send.nil?
29
+
30
+ send_raw(body)
31
+ end
32
+
33
+ def send_raw(request)
34
+ request = request.dup
35
+ request["idempotency_key"] ||= Client.generate_idempotency_key
36
+ request(:post, "/v1/send", body: request) || {}
37
+ end
38
+
39
+ def send_batch(requests)
40
+ body = requests.map do |req|
41
+ req = req.dup
42
+ req["idempotency_key"] ||= Client.generate_idempotency_key
43
+ req
44
+ end
45
+ request(:post, "/v1/send/batch", body: body) || []
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendara
4
+ module Resources
5
+ class Lists < Resource
6
+ def list(limit: nil, offset: nil)
7
+ query = compact_params("limit" => limit, "offset" => offset)
8
+ response = request(:get, "/v1/contacts/lists", query: query) || {}
9
+ lists = response["lists"]
10
+ lists.is_a?(Array) ? lists : []
11
+ end
12
+
13
+ def get(id)
14
+ request(:get, "/v1/contacts/lists/#{encode(id)}") || {}
15
+ end
16
+
17
+ def create(name:, list_type: nil, segment_rules: nil)
18
+ body = compact_params(
19
+ "name" => name,
20
+ "list_type" => list_type,
21
+ "segment_rules" => segment_rules
22
+ )
23
+ request(:post, "/v1/contacts/lists", body: body) || {}
24
+ end
25
+
26
+ def update(id, name: nil, segment_rules: nil)
27
+ body = compact_params(
28
+ "name" => name,
29
+ "segment_rules" => segment_rules
30
+ )
31
+ request(:put, "/v1/contacts/lists/#{encode(id)}", body: body) || {}
32
+ end
33
+
34
+ def delete(id)
35
+ request(:delete, "/v1/contacts/lists/#{encode(id)}")
36
+ nil
37
+ end
38
+
39
+ def add_member(id, contact_id)
40
+ request(:post, "/v1/contacts/lists/#{encode(id)}/members",
41
+ body: { "contact_id" => contact_id }) || {}
42
+ end
43
+
44
+ def remove_member(id, contact_id)
45
+ request(:delete, "/v1/contacts/lists/#{encode(id)}/members/#{encode(contact_id)}")
46
+ nil
47
+ end
48
+
49
+ def members(id)
50
+ response = request(:get, "/v1/contacts/lists/#{encode(id)}/members") || {}
51
+ members = response["members"]
52
+ members.is_a?(Array) ? members : []
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendara
4
+ module Resources
5
+ class Messages < Resource
6
+ include Enumerable
7
+
8
+ def page(channel: nil, status: nil, from: nil, to: nil, limit: nil, cursor: nil)
9
+ query = compact_params(
10
+ "channel" => channel,
11
+ "status" => status,
12
+ "from" => from,
13
+ "to" => to,
14
+ "limit" => limit,
15
+ "cursor" => cursor
16
+ )
17
+ response = request(:get, "/v1/messages", query: query) || {}
18
+ MessagePage.from_response(response)
19
+ end
20
+
21
+ def each(channel: nil, status: nil, from: nil, to: nil, limit: nil, cursor: nil, &block)
22
+ unless block_given?
23
+ return enum_for(:each, channel: channel, status: status, from: from,
24
+ to: to, limit: limit, cursor: cursor)
25
+ end
26
+
27
+ loop do
28
+ current = page(channel: channel, status: status, from: from,
29
+ to: to, limit: limit, cursor: cursor)
30
+ current.messages.each(&block)
31
+ break if current.next_cursor.nil?
32
+
33
+ cursor = current.next_cursor
34
+ end
35
+ end
36
+
37
+ def get(id)
38
+ request(:get, "/v1/messages/#{encode(id)}") || {}
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendara
4
+ module Resources
5
+ class Suppressions < Resource
6
+ def list(channel: nil)
7
+ query = compact_params("channel" => channel)
8
+ response = request(:get, "/v1/suppressions", query: query) || {}
9
+ suppressions = response["suppressions"]
10
+ suppressions.is_a?(Array) ? suppressions : []
11
+ end
12
+
13
+ def create(channel:, recipient:, reason: nil)
14
+ body = compact_params(
15
+ "channel" => channel,
16
+ "recipient" => recipient,
17
+ "reason" => reason
18
+ )
19
+ request(:post, "/v1/suppressions", body: body) || {}
20
+ end
21
+
22
+ def delete(channel, recipient)
23
+ request(:delete, "/v1/suppressions",
24
+ query: { "channel" => channel, "recipient" => recipient })
25
+ nil
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendara
4
+ module Resources
5
+ class Templates < Resource
6
+ def list
7
+ response = request(:get, "/v1/templates") || {}
8
+ templates = response["templates"]
9
+ templates.is_a?(Array) ? templates : []
10
+ end
11
+
12
+ def get(id)
13
+ request(:get, "/v1/templates/#{encode(id)}") || {}
14
+ end
15
+
16
+ def create(name:, channel: "email", subject: nil, body_text: nil, body_html: nil,
17
+ body_json: nil, variables: nil)
18
+ body = compact_params(
19
+ "name" => name,
20
+ "channel" => channel,
21
+ "subject" => subject,
22
+ "body_text" => body_text,
23
+ "body_html" => body_html,
24
+ "body_json" => body_json,
25
+ "variables" => variables
26
+ )
27
+ request(:post, "/v1/templates", body: body) || {}
28
+ end
29
+
30
+ def update(id, name: nil, subject: nil, body_text: nil, body_html: nil,
31
+ body_json: nil, variables: nil, is_active: nil)
32
+ body = compact_params(
33
+ "name" => name,
34
+ "subject" => subject,
35
+ "body_text" => body_text,
36
+ "body_html" => body_html,
37
+ "body_json" => body_json,
38
+ "variables" => variables,
39
+ "is_active" => is_active
40
+ )
41
+ request(:put, "/v1/templates/#{encode(id)}", body: body) || {}
42
+ end
43
+
44
+ def delete(id)
45
+ request(:delete, "/v1/templates/#{encode(id)}")
46
+ nil
47
+ end
48
+
49
+ def render(id, vars = {})
50
+ request(:post, "/v1/templates/#{encode(id)}/render", body: { "vars" => vars || {} }) || {}
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendara
4
+ module Resources
5
+ class Usage < Resource
6
+ def get(period: nil)
7
+ query = compact_params("period" => period)
8
+ request(:get, "/v1/usage", query: query) || {}
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendara
4
+ VERSION = "0.1.0"
5
+ end