sendio 1.0.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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +31 -0
  3. data/LICENSE +22 -0
  4. data/NOTICE +1 -0
  5. data/README.md +210 -0
  6. data/app/controllers/action_mailbox/ingresses/sendio/inbound_emails_controller.rb +95 -0
  7. data/lib/sendio/action_mailbox/engine.rb +19 -0
  8. data/lib/sendio/action_mailbox/message_builder.rb +162 -0
  9. data/lib/sendio/action_mailbox.rb +5 -0
  10. data/lib/sendio/api_keys.rb +32 -0
  11. data/lib/sendio/automations/runs.rb +22 -0
  12. data/lib/sendio/automations/templates.rb +29 -0
  13. data/lib/sendio/automations.rb +64 -0
  14. data/lib/sendio/batch.rb +36 -0
  15. data/lib/sendio/broadcasts.rb +180 -0
  16. data/lib/sendio/client.rb +19 -0
  17. data/lib/sendio/contact_properties.rb +106 -0
  18. data/lib/sendio/contacts/imports.rb +64 -0
  19. data/lib/sendio/contacts/segments.rb +66 -0
  20. data/lib/sendio/contacts/topics.rb +80 -0
  21. data/lib/sendio/contacts.rb +131 -0
  22. data/lib/sendio/domains.rb +60 -0
  23. data/lib/sendio/emails/attachments.rb +67 -0
  24. data/lib/sendio/emails/receiving/attachments.rb +69 -0
  25. data/lib/sendio/emails/receiving.rb +51 -0
  26. data/lib/sendio/emails.rb +124 -0
  27. data/lib/sendio/errors.rb +59 -0
  28. data/lib/sendio/events.rb +46 -0
  29. data/lib/sendio/logs.rb +20 -0
  30. data/lib/sendio/mailboxes.rb +87 -0
  31. data/lib/sendio/mailer.rb +241 -0
  32. data/lib/sendio/multipart_request.rb +67 -0
  33. data/lib/sendio/oauth_grants.rb +20 -0
  34. data/lib/sendio/pagination_helper.rb +30 -0
  35. data/lib/sendio/railtie.rb +14 -0
  36. data/lib/sendio/request.rb +122 -0
  37. data/lib/sendio/response.rb +154 -0
  38. data/lib/sendio/segments.rb +38 -0
  39. data/lib/sendio/suppressions/batch.rb +44 -0
  40. data/lib/sendio/suppressions.rb +64 -0
  41. data/lib/sendio/templates.rb +50 -0
  42. data/lib/sendio/topics.rb +38 -0
  43. data/lib/sendio/usage.rb +14 -0
  44. data/lib/sendio/version.rb +5 -0
  45. data/lib/sendio/webhooks.rb +329 -0
  46. data/lib/sendio.rb +78 -0
  47. metadata +119 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3b82f47130797ce62b2a01860ba0b682f084aad772203c5e82085fe6b3f4ecf3
4
+ data.tar.gz: bb2edfd4bd16576f826c46f48d5745a60481b321609e2cec6338c4aafa9c9bab
5
+ SHA512:
6
+ metadata.gz: aaaf1d140cdf58e068bc5b21d54865e202afbaee0921361c30d686faa8c8d5f72217b0dfd499679e0036e33cea5f100ecfb6f620d28a612894d2fb5e8e073769
7
+ data.tar.gz: cfca683fc0f253d84325d0319d76c86c0a3e077548ce35619bfbe3c56f26571d3ef2993ff493b581a2ee06f97401028159d32def6e8a750395e6b8345b38b031
data/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2026-11-11
9
+
10
+ First public release.
11
+
12
+ ### Removed
13
+
14
+ - `Sendio::Domains::Claims` (`.create`, `.get`, `.verify`): the API has no domain-claim routes, so every call failed with a 404. Claim a domain by deleting it from the team that owns it, then adding it to yours.
15
+
16
+ ### Added
17
+
18
+ - `Sendio::Automations::Templates.list` (optional `category:` filter) and `.get(slug)` to browse the automation template catalog, and `Sendio::Automations.create_from_template(params, options: { idempotency_key: })` to create a disabled automation from a template.
19
+ - `Sendio::Mailboxes` with `.list`, `.create`, `.update`, `.reset_password` and `.remove`, to manage mailboxes (email accounts with an IMAP/SMTP login).
20
+ - `Sendio::Broadcasts.estimate`, `.test`, `.stats`, `.list_versions`, `.get_version`, `.update_version` and `.restore_version`.
21
+ - Documented `capabilities: { receiving: "enabled" | "disabled" }` on `Sendio::Domains.create` and `Sendio::Domains.update` to turn email receiving on or off for a domain (turning it on asks for the domain's MX record, publish it to start receiving email; sending can't be disabled).
22
+ - Documented the full set of broadcast create/update fields (`segment_ids`, `exclude_segment_ids`, `audience`, `unsubscribe_scope`, `tracking`, `template_engine`, `topic_id`, `preview_text`) and the `status`, `segment_id` and `search` list filters.
23
+
24
+ ### Fixed
25
+
26
+ - `Sendio::Broadcasts.update` and `.send` no longer send `broadcast_id` in the JSON body; it goes only in the path (the API rejects any key but `name` when updating a sent broadcast).
27
+
28
+ ### Added
29
+
30
+ - First release of the official send.io Ruby SDK (`sendio` gem).
31
+ - Default API base URL `https://api.send.io`, overridable with `SENDIO_BASE_URL` or `Sendio.base_url = "..."` at runtime (trailing slash optional).
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Derich Pacheco and Resend
4
+ Copyright (c) 2026 send.io
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
data/NOTICE ADDED
@@ -0,0 +1 @@
1
+ This SDK is based on https://github.com/resend/resend-ruby (MIT License), Copyright (c) Resend.
data/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # send.io Ruby and Rails SDK
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
4
+ [![Gem Version](https://badge.fury.io/rb/sendio.svg)](https://badge.fury.io/rb/sendio)
5
+
6
+ The official Ruby and Rails SDK for the [send.io](https://send.io) email API. Full API reference: <https://developer.send.io/docs/api>.
7
+
8
+ ## Installation
9
+
10
+ Via RubyGems:
11
+
12
+ ```sh
13
+ gem install sendio
14
+ ```
15
+
16
+ Via Gemfile:
17
+
18
+ ```ruby
19
+ gem "sendio"
20
+ ```
21
+
22
+ Requires Ruby 3.2 or newer.
23
+
24
+ ## Quick start
25
+
26
+ Create an API key in the send.io dashboard (keys start with `mx_`), then:
27
+
28
+ ```ruby
29
+ require "sendio"
30
+
31
+ Sendio.api_key = ENV["SENDIO_API_KEY"]
32
+
33
+ email = Sendio::Emails.send({
34
+ from: "Acme <onboarding@yourdomain.com>",
35
+ to: ["delivered@send.io"],
36
+ subject: "Hello from send.io",
37
+ html: "<strong>It works!</strong>"
38
+ })
39
+
40
+ puts email[:id]
41
+ ```
42
+
43
+ ## Send a batch
44
+
45
+ Up to 100 emails in one request:
46
+
47
+ ```ruby
48
+ Sendio::Batch.send([
49
+ {
50
+ from: "Acme <onboarding@yourdomain.com>",
51
+ to: ["delivered@send.io"],
52
+ subject: "First",
53
+ html: "<p>one</p>"
54
+ },
55
+ {
56
+ from: "Acme <onboarding@yourdomain.com>",
57
+ to: ["delivered@send.io"],
58
+ subject: "Second",
59
+ html: "<p>two</p>"
60
+ }
61
+ ])
62
+ ```
63
+
64
+ ## Retrieve an email
65
+
66
+ ```ruby
67
+ email = Sendio::Emails.get("4ef9a417-02e9-4d39-ad75-9611e0fcc33c")
68
+ puts email[:last_event]
69
+
70
+ Sendio::Emails.list(limit: 10)
71
+ ```
72
+
73
+ ## Domains
74
+
75
+ ```ruby
76
+ domain = Sendio::Domains.create({ name: "yourdomain.com" })
77
+ Sendio::Domains.verify(domain[:id])
78
+ Sendio::Domains.get(domain[:id])
79
+ Sendio::Domains.list
80
+ Sendio::Domains.remove(domain[:id])
81
+
82
+ # Receive email on the domain too (send.io then requires its MX record)
83
+ domain = Sendio::Domains.create({ name: "yourdomain.com", capabilities: { receiving: "enabled" } })
84
+
85
+ # Stop receiving: send.io stops accepting email for the domain; remove its MX record
86
+ Sendio::Domains.update({ id: domain[:id], capabilities: { receiving: "disabled" } })
87
+
88
+ # Turn it back on (asks for the domain's MX record; publish it to start receiving email)
89
+ Sendio::Domains.update({ id: domain[:id], capabilities: { receiving: "enabled" } })
90
+ ```
91
+
92
+ ## Mailboxes
93
+
94
+ Mailboxes are real email accounts with an IMAP/SMTP login. The domain must be verified.
95
+
96
+ ```ruby
97
+ mailbox = Sendio::Mailboxes.create({ address: "support@yourdomain.com" })
98
+ puts mailbox[:credentials] # shown once; the password is also an API key for this mailbox
99
+
100
+ Sendio::Mailboxes.list
101
+ Sendio::Mailboxes.update({ mailbox_id: mailbox[:id], warmup_enabled: false })
102
+ Sendio::Mailboxes.reset_password(mailbox[:id]) # the old password stops working
103
+ Sendio::Mailboxes.remove(mailbox[:id]) # also removes an agent inbox on it
104
+ ```
105
+
106
+ Sending is always on; `capabilities: { sending: "disabled" }` is rejected with a 422.
107
+
108
+ The SDK also covers API keys, segments, contacts, contact properties, topics, broadcasts, templates, webhooks, received emails, logs, suppressions and more. See the [examples folder](examples/) for one script per resource.
109
+
110
+ ## Configuration
111
+
112
+ ### API key
113
+
114
+ ```ruby
115
+ Sendio.api_key = ENV["SENDIO_API_KEY"]
116
+
117
+ # or
118
+ Sendio.configure do |config|
119
+ config.api_key = ENV["SENDIO_API_KEY"]
120
+ end
121
+
122
+ # or lazily, evaluated on every request
123
+ Sendio.api_key = -> { Current.user.sendio_api_key }
124
+ ```
125
+
126
+ ### Base URL
127
+
128
+ Requests go to `https://api.send.io` by default. Override it (for testing or a different environment) with the `SENDIO_BASE_URL` environment variable or at runtime. A trailing slash is optional:
129
+
130
+ ```ruby
131
+ # environment: SENDIO_BASE_URL=https://api.example.test
132
+ Sendio.base_url = "https://api.example.test"
133
+ Sendio.base_url = nil # back to SENDIO_BASE_URL or the default
134
+ ```
135
+
136
+ ## Error handling
137
+
138
+ Non-2xx responses raise a subclass of `Sendio::Error`:
139
+
140
+ ```ruby
141
+ begin
142
+ Sendio::Emails.send({ from: "bad", to: ["delivered@send.io"], subject: "x", html: "x" })
143
+ rescue Sendio::Error::RateLimitExceededError => e
144
+ sleep e.retry_after.to_i
145
+ rescue Sendio::Error::InvalidRequestError, Sendio::Error::NotFoundError => e
146
+ warn "request rejected: #{e.message}"
147
+ rescue Sendio::Error => e
148
+ warn "send.io error: #{e.message}"
149
+ end
150
+ ```
151
+
152
+ ## Rails and ActionMailer
153
+
154
+ Use send.io as an ActionMailer delivery method in `config/environments/production.rb`:
155
+
156
+ ```ruby
157
+ config.action_mailer.delivery_method = :sendio
158
+ ```
159
+
160
+ Set your API key in an initializer:
161
+
162
+ ```ruby
163
+ # config/initializers/sendio.rb
164
+ Sendio.api_key = ENV["SENDIO_API_KEY"]
165
+ ```
166
+
167
+ Then deliver as usual:
168
+
169
+ ```ruby
170
+ class UserMailer < ApplicationMailer
171
+ default from: "Acme <onboarding@yourdomain.com>"
172
+
173
+ def welcome_email
174
+ @user = params[:user]
175
+ mail(to: ["delivered@send.io"], subject: "Welcome")
176
+ end
177
+ end
178
+
179
+ UserMailer.with(user: user).welcome_email.deliver_now!
180
+ ```
181
+
182
+ ## Rails and Action Mailbox
183
+
184
+ The gem ships an Action Mailbox ingress so you can receive inbound email through send.io:
185
+
186
+ ```ruby
187
+ # config/environments/production.rb
188
+ config.action_mailbox.ingress = :sendio
189
+ ```
190
+
191
+ Create a webhook (in the dashboard or with `Sendio::Webhooks.create`) subscribed to `email.received` and pointed at:
192
+
193
+ ```
194
+ https://example.com/rails/action_mailbox/sendio/inbound_emails
195
+ ```
196
+
197
+ Store the webhook signing secret (`whsec_...`) under `action_mailbox.sendio_signing_secret` in your Rails credentials, or in the `SENDIO_INGRESS_SIGNING_SECRET` environment variable. Each verified webhook is turned back into the full email and routed like any other ingress.
198
+
199
+ ## Development
200
+
201
+ ```sh
202
+ bundle install
203
+ bundle exec rspec
204
+ ```
205
+
206
+ `examples/smoke.rb` is a live check against a real API (sends one email). It needs `SENDIO_API_KEY` and, optionally, `SENDIO_BASE_URL`.
207
+
208
+ ## License
209
+
210
+ MIT. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMailbox
4
+ module Ingresses
5
+ module Sendio
6
+ # Ingests inbound emails delivered by send.io's +email.received+ webhook.
7
+ #
8
+ # send.io webhooks only carry the received email's metadata, so the full message
9
+ # (including any attachments) is fetched from the send.io API before being enqueued
10
+ # for routing. The webhook request is authenticated by verifying its Svix signature
11
+ # against the webhook's signing secret, which is stored in the
12
+ # +action_mailbox.sendio_signing_secret+ Rails credential or the
13
+ # +SENDIO_INGRESS_SIGNING_SECRET+ environment variable.
14
+ #
15
+ # Returns:
16
+ #
17
+ # - <tt>204 No Content</tt> if an inbound email is successfully recorded and enqueued for
18
+ # routing, or if the event is not an +email.received+ event and was ignored
19
+ # - <tt>401 Unauthorized</tt> if the request's signature could not be validated
20
+ # - <tt>404 Not Found</tt> if Action Mailbox is not configured to accept inbound emails
21
+ # from Sendio
22
+ # - <tt>422 Unprocessable Entity</tt> if the request's payload is malformed
23
+ # - <tt>500 Server Error</tt> if the webhook signing secret is missing, the send.io API
24
+ # could not be reached, or one of the Active Record database, the Active Storage
25
+ # service, or the Active Job backend is misconfigured or unavailable
26
+ class InboundEmailsController < ActionMailbox::BaseController
27
+ before_action :authenticate
28
+
29
+ def create
30
+ return head :no_content unless email_received_event?
31
+ return head :unprocessable_entity if email_id.nil?
32
+
33
+ ActionMailbox::InboundEmail.create_and_extract_message_id! raw_email
34
+ head :no_content
35
+ rescue JSON::ParserError => e
36
+ logger.error e.message
37
+ head :unprocessable_entity
38
+ end
39
+
40
+ private
41
+
42
+ def raw_email
43
+ ::Sendio::ActionMailbox::MessageBuilder.new(email_id).raw_email
44
+ end
45
+
46
+ def email_received_event?
47
+ event["type"] == "email.received"
48
+ end
49
+
50
+ def email_id
51
+ event.dig("data", "email_id")
52
+ end
53
+
54
+ def event
55
+ @event ||= JSON.parse(request.raw_post)
56
+ end
57
+
58
+ def authenticate
59
+ head :unauthorized unless authenticated?
60
+ end
61
+
62
+ def authenticated?
63
+ if signing_secret.present?
64
+ verified_signature?
65
+ else
66
+ raise ArgumentError, <<~MESSAGE.squish
67
+ Missing required Sendio webhook signing secret. Set action_mailbox.sendio_signing_secret
68
+ in your application's encrypted credentials or provide the SENDIO_INGRESS_SIGNING_SECRET
69
+ environment variable.
70
+ MESSAGE
71
+ end
72
+ end
73
+
74
+ def verified_signature?
75
+ ::Sendio::Webhooks.verify(
76
+ payload: request.raw_post,
77
+ headers: {
78
+ svix_id: request.headers["svix-id"],
79
+ svix_timestamp: request.headers["svix-timestamp"],
80
+ svix_signature: request.headers["svix-signature"]
81
+ },
82
+ webhook_secret: signing_secret
83
+ )
84
+ rescue RuntimeError
85
+ false
86
+ end
87
+
88
+ def signing_secret
89
+ Rails.application.credentials.dig(:action_mailbox, :sendio_signing_secret) ||
90
+ ENV["SENDIO_INGRESS_SIGNING_SECRET"]
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/engine"
4
+
5
+ module Sendio
6
+ module ActionMailbox
7
+ # Rails engine that routes send.io's +email.received+ webhooks to the
8
+ # Action Mailbox ingress controller.
9
+ class Engine < ::Rails::Engine
10
+ initializer "sendio.action_mailbox.routes" do |app|
11
+ app.routes.append do
12
+ post "/rails/action_mailbox/sendio/inbound_emails",
13
+ to: "action_mailbox/ingresses/sendio/inbound_emails#create",
14
+ as: :rails_sendio_inbound_emails
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mail"
4
+
5
+ module Sendio
6
+ module ActionMailbox
7
+ # Rebuilds the full RFC 822 source of a received email so it can be ingested
8
+ # by Action Mailbox.
9
+ #
10
+ # send.io's +email.received+ webhook only carries metadata, so the message is
11
+ # fetched from the Received Emails API. When the API exposes a raw message
12
+ # download it is used as-is; otherwise the message is reconstructed as a
13
+ # Mail::Message from its parts, downloading each attachment through the
14
+ # attachments API.
15
+ class MessageBuilder
16
+ # Headers that describe the reconstructed MIME structure and therefore
17
+ # cannot be copied verbatim from the original message.
18
+ STRUCTURAL_HEADERS = %w[content-type content-transfer-encoding mime-version].freeze
19
+
20
+ # Envelope fields that are set from the API response when the original
21
+ # header of the same name is not available.
22
+ ENVELOPE_FIELDS = %i[from to cc bcc reply_to subject message_id].freeze
23
+
24
+ # @param email_id [String] The ID of the received email
25
+ def initialize(email_id)
26
+ @email_id = email_id
27
+ end
28
+
29
+ # @return [String] the full RFC 822 source of the received email
30
+ def raw_email
31
+ raw_download || rebuild_message.to_s
32
+ end
33
+
34
+ private
35
+
36
+ def email
37
+ @email ||= Sendio::Emails::Receiving.get(@email_id, html_format: "cid")
38
+ end
39
+
40
+ def raw_download
41
+ url = fetch(email[:raw], "download_url") unless blank?(email[:raw])
42
+ download(url) unless blank?(url)
43
+ end
44
+
45
+ def rebuild_message
46
+ Mail.new.tap do |mail|
47
+ copy_headers(mail)
48
+ copy_envelope(mail)
49
+ add_bodies(mail)
50
+ add_attachments(mail)
51
+ end
52
+ end
53
+
54
+ def copy_headers(mail)
55
+ (email[:headers] || {}).each do |name, value|
56
+ next if STRUCTURAL_HEADERS.include?(name.to_s.downcase)
57
+
58
+ Array(value).each { |val| mail.header[name.to_s] = val }
59
+ end
60
+ end
61
+
62
+ def copy_envelope(mail)
63
+ ENVELOPE_FIELDS.each do |field|
64
+ value = email[field]
65
+ next if blank?(value) || mail.header[field.to_s.tr("_", "-")]
66
+
67
+ mail.public_send("#{field}=", value)
68
+ end
69
+
70
+ mail.date ||= email[:created_at]
71
+
72
+ expose_bcc(mail)
73
+ copy_received_for(mail)
74
+ end
75
+
76
+ # Mail omits Bcc when serializing a message by default, but Action Mailbox
77
+ # needs it to route the email.
78
+ def expose_bcc(mail)
79
+ bcc = mail.header["bcc"]
80
+ bcc.include_in_headers = true if bcc.respond_to?(:include_in_headers)
81
+ end
82
+
83
+ # Preserve the addresses this email was received for (e.g. through a
84
+ # forwarding rule) so Action Mailbox can route on them.
85
+ def copy_received_for(mail)
86
+ Array(email[:received_for]).each { |address| mail.header["X-Original-To"] = address }
87
+ end
88
+
89
+ def add_bodies(mail)
90
+ html = presence(email[:html])
91
+ text = presence(email[:text])
92
+
93
+ if html
94
+ mail.text_part = build_part("text/plain", text) if text
95
+ mail.html_part = build_part("text/html", html)
96
+ elsif text
97
+ mail.content_type "text/plain; charset=UTF-8"
98
+ mail.body text
99
+ end
100
+ end
101
+
102
+ def build_part(mime_type, content)
103
+ Mail::Part.new(content_type: "#{mime_type}; charset=UTF-8", body: content)
104
+ end
105
+
106
+ def add_attachments(mail)
107
+ Array(email[:attachments]).each { |meta| add_attachment(mail, meta) }
108
+ end
109
+
110
+ def add_attachment(mail, meta)
111
+ details = Sendio::Emails::Receiving::Attachments.get(email_id: @email_id, id: fetch(meta, "id"))
112
+ filename = fetch(meta, "filename") || details[:filename]
113
+
114
+ mail.attachments[filename] = {
115
+ mime_type: fetch(meta, "content_type") || details[:content_type],
116
+ content: download(details[:download_url])
117
+ }
118
+
119
+ decorate_attachment(mail.attachments.last, meta)
120
+ end
121
+
122
+ def decorate_attachment(part, meta)
123
+ content_id = fetch(meta, "content_id")
124
+ part.content_id = normalize_content_id(content_id) unless blank?(content_id)
125
+
126
+ return unless fetch(meta, "content_disposition") == "inline"
127
+
128
+ part.content_disposition = "inline; filename=\"#{part.filename}\""
129
+ end
130
+
131
+ def normalize_content_id(content_id)
132
+ content_id.start_with?("<") ? content_id : "<#{content_id}>"
133
+ end
134
+
135
+ def download(url)
136
+ response = HTTParty.get(url)
137
+
138
+ unless response.code == 200
139
+ raise Sendio::Error::ServerError.new(
140
+ "Failed to download received email content (HTTP #{response.code})", response.code
141
+ )
142
+ end
143
+
144
+ response.body
145
+ end
146
+
147
+ # Nested objects come back from the API with string keys, but hand-built
148
+ # hashes (tests, console usage) often use symbols. Accept both.
149
+ def fetch(hash, key)
150
+ hash[key] || hash[key.to_sym]
151
+ end
152
+
153
+ def presence(value)
154
+ value unless blank?(value)
155
+ end
156
+
157
+ def blank?(value)
158
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sendio"
4
+ require "sendio/action_mailbox/message_builder"
5
+ require "sendio/action_mailbox/engine" if defined?(::Rails::Engine)
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendio
4
+ # api keys api wrapper
5
+ module ApiKeys
6
+ class << self
7
+ # https://developer.send.io/docs/api
8
+ def create(params)
9
+ path = "api-keys"
10
+ Sendio::Request.new(path, params, "post").perform
11
+ end
12
+
13
+ # https://developer.send.io/docs/api
14
+ def list(params = {})
15
+ path = Sendio::PaginationHelper.build_paginated_path("api-keys", params)
16
+ Sendio::Request.new(path, {}, "get").perform
17
+ end
18
+
19
+ # https://developer.send.io/docs/api
20
+ def remove(api_key_id = "")
21
+ path = "api-keys/#{api_key_id}"
22
+ Sendio::Request.new(path, {}, "delete").perform
23
+ end
24
+
25
+ # https://developer.send.io/docs/api
26
+ def update(params)
27
+ path = "api-keys/#{params[:id]}"
28
+ Sendio::Request.new(path, { name: params[:name] }, "patch").perform
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendio
4
+ module Automations
5
+ # Automation Runs api wrapper
6
+ module Runs
7
+ class << self
8
+ # https://developer.send.io/docs/api
9
+ def list(automation_id, params = {})
10
+ base_path = "automations/#{automation_id}/runs"
11
+ path = Sendio::PaginationHelper.build_paginated_path(base_path, params)
12
+ Sendio::Request.new(path, {}, "get").perform
13
+ end
14
+
15
+ # https://developer.send.io/docs/api
16
+ def get(automation_id, run_id)
17
+ Sendio::Request.new("automations/#{automation_id}/runs/#{run_id}", {}, "get").perform
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sendio
4
+ module Automations
5
+ # Automation Templates api wrapper (ready-made automations you can create from)
6
+ module Templates
7
+ class << self
8
+ # List the automation template catalog. Not paginated.
9
+ # see more: https://developer.send.io/docs/api
10
+ #
11
+ # @param params [Hash] Optional parameters
12
+ # @option params [String] :category Only templates in this category: "onboarding",
13
+ # "billing", "ecommerce", "engagement", "lead_capture" or "internal"
14
+ def list(params = {})
15
+ query_params = {}
16
+ query_params[:category] = params[:category] if params[:category]
17
+ Sendio::Request.new("automation-templates", query_params, "get").perform
18
+ end
19
+
20
+ # Retrieve one automation template in full (how_it_works, events with schemas,
21
+ # emails and params).
22
+ # see more: https://developer.send.io/docs/api
23
+ def get(slug)
24
+ Sendio::Request.new("automation-templates/#{ERB::Util.url_encode(slug.to_s)}", {}, "get").perform
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end