wakio 0.1.2 → 0.1.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 701b8f414dc39686af35c768fd62f176365f50e8e10cf5fc8d47fe8473fad702
4
- data.tar.gz: ab412723cc02b1ce9e04c9fdcb51b0282f467d1f43dacbd415a4df4532111406
3
+ metadata.gz: 38874499f377ba462c3e6b6309132fc3e755f83d53ba173c3f45c6f3c7a71b22
4
+ data.tar.gz: dd55b75c7b3d5ba2dd490c6d24c18bf284b4c389385e14627e840b5d73e56ea7
5
5
  SHA512:
6
- metadata.gz: 1e5191bf7700df69a86c119fd2dadbc4e8ef26e5530b68dd60727057f6f9c08e6e6e1928040001986585dd9ebf4ea9f9032da6561687330dff4f887c654aea85
7
- data.tar.gz: 20c01bf3d406b7431632aac6367ecb227fc735fef780cba4f4c760db1df898fbc34b5def97e0c46f96ed1742b9e5c575666b180845300892e8fe423566cb16ea
6
+ metadata.gz: 96d287db49a082bdd79f41500482a7141e0aedae7ed1f7508ae070321a4ab10ac133496f6ba3214b328b780b13042a61e7d2f600e86a397337dfe0773047f6fe
7
+ data.tar.gz: 92402e1b1906380ede53db33c50f1ba806d746d8d63f9b3b8760105427e7a3349093dbfc46b1172ca1bdca07a73b4c671e6f7f48feea85930ea8f57053b97d5a
data/README.md CHANGED
@@ -14,9 +14,11 @@ config.action_mailer.wakio_settings = {
14
14
  }
15
15
  ```
16
16
 
17
- `open_timeout` and `read_timeout` are optional, in seconds, and default to 5 and 10. A timeout raises `Net::OpenTimeout` or `Net::ReadTimeout`, not `Wakio::DeliveryError`. A read timeout can come after Wakio accepted the send, so a retry without an idempotency key (below) may send twice.
17
+ `open_timeout` and `read_timeout` are optional, in seconds, and default to 5 and 10. A timeout raises `Net::OpenTimeout` or `Net::ReadTimeout`, and any other network failure raises Ruby's own error, not `Wakio::DeliveryError`. A read timeout can come after Wakio accepted the send, so a retry without an idempotency key (below) may send twice.
18
18
 
19
- Every existing mailer keeps working unchanged. Its rendered subject, its HTML and text parts, and its `from` go to Wakio as they are, with no server-side template involved. A text-only mailer sends a text-only email.
19
+ `host` must be `https`. An `http` host other than `localhost` raises `Wakio::Misconfigured` before anything is sent, so the API key never travels in cleartext.
20
+
21
+ Every existing mailer keeps working unchanged. Its rendered subject, its HTML and text parts, and its `from` go to Wakio as they are, with no server-side template involved. A text-only mailer sends a text-only email. Recipients keep their display names (`to`, `cc`, `bcc`); `reply_to` is sent as bare addresses.
20
22
 
21
23
  The `from` domain must be one of your account's sendable sending domains, matched exactly: `hello@acme.com` needs `acme.com` itself verified, and a verified `mail.acme.com` does not cover it. A mismatch is refused with `422 no_verified_sending_domain`. A mailer with no `from` goes out as `no-reply@` your first sendable domain. `GET /v1/sending-identities` reports which domains are currently sendable.
22
24
 
@@ -24,11 +26,26 @@ The `from` domain must be one of your account's sendable sending domains, matche
24
26
 
25
27
  **A suppressed address raises.** Wakio suppresses addresses that have hard-bounced or complained, and accepts a send to one rather than refusing it. The response names each recipient's status, and `deliver!` raises `Wakio::Suppressed` (a `Wakio::DeliveryError`) when every recipient was suppressed, so a one-recipient mailer to a suppressed address fails loudly instead of reporting success. Retrying cannot help, so handle it rather than letting a job backend retry it: `rescue_from Wakio::Suppressed` in your mailer also catches it under `deliver_later`. A send where only some recipients were suppressed returns normally; check the returned response's `deliveries` for which ones.
26
28
 
27
- **Refusals split by whether a retry can ever send the message.** Every error is a `Wakio::DeliveryError` carrying `status`, `code` (the response's `error`, such as `"invalid_request"`) and the parsed `body`.
29
+ **Refusals split by whether a retry can ever send the message.** A refusal from Wakio is a `Wakio::DeliveryError` carrying `status`, `code` (the response's `error`, such as `"invalid_request"`) and the parsed `body`. `code` is nil when the response is not JSON or names no `error`, so fall back to `status` when you log it.
30
+
31
+ - `Wakio::Rejected`: retrying this message cannot help. `400`, `413`, `422 invalid_request` (usually a mailer bug; `detail` names the field) and `422 idempotency_key_reused` (an earlier request with that key already went out). Report it and drop it.
32
+ - `Wakio::Misconfigured`: the account or its configuration is wrong, and the same message sends once that is fixed. `401 invalid_api_key`, `402 not_entitled`, `403 account_suspended`, `404`, `422 no_verified_sending_domain`, `422 unknown_template`, a redirect, an `http` host, and any 4xx this gem does not know. Retry it.
33
+ - A plain `Wakio::DeliveryError`: `409 request_in_flight`, every 5xx, and a success response the gem cannot read. Retry it; after an unreadable success only a keyed send is safe to repeat.
34
+
35
+ Action Mailer's delivery job retries nothing itself, and Solid Queue and GoodJob do not retry a failed job by default, so give it a policy:
36
+
37
+ ```ruby
38
+ class WakioDeliveryJob < ActionMailer::MailDeliveryJob
39
+ retry_on Wakio::DeliveryError, Net::OpenTimeout, Net::ReadTimeout, wait: :polynomially_longer, attempts: 25
40
+ end
41
+
42
+ # config/application.rb
43
+ config.action_mailer.delivery_job = "WakioDeliveryJob"
44
+ ```
45
+
46
+ On Sidekiq, skip that job. Sidekiq already retries every failed job, about 25 times over three weeks, so the risk runs the other way: a message that can never send is retried for weeks. What you need there is the `rescue_from` below, which stops `Suppressed` and `Rejected` from reaching Sidekiq at all.
28
47
 
29
- - `Wakio::Rejected`: this message can never be sent. `400`, `413`, `422 invalid_request` and `422 idempotency_key_reused`. Report it and drop it.
30
- - `Wakio::Misconfigured`: the account or its configuration is wrong, and the same message sends once that is fixed. `401 invalid_api_key`, `402 not_entitled`, `403 account_suspended`, `404`, `422 no_verified_sending_domain`, `422 unknown_template`, and any 4xx this gem does not know. Let it raise so the job retries and your error tracker shows it.
31
- - A plain `Wakio::DeliveryError`: `409 request_in_flight` and every 5xx. Let it retry.
48
+ The mailer's `rescue_from` below catches `Suppressed` and `Rejected` before the job sees them.
32
49
 
33
50
  ```ruby
34
51
  class ApplicationMailer < ActionMailer::Base
@@ -37,6 +37,8 @@ module Wakio
37
37
  # towards retrying rather than towards dropping mail.
38
38
  REJECTED_STATUSES = [ 400, 413 ].freeze
39
39
  REJECTED_CODES = %w[invalid_request idempotency_key_reused].freeze
40
+ LOCAL_HOSTS = %w[localhost 127.0.0.1 ::1].freeze
41
+ MESSAGE_BODY_LIMIT = 300
40
42
 
41
43
  def deliver!(mail)
42
44
  # Caller-supplied, never derived — and no key at all when the caller gives none.
@@ -71,9 +73,9 @@ module Wakio
71
73
  def payload_for(mail)
72
74
  html, text = bodies_for(mail)
73
75
  {
74
- to: Array(mail.to),
75
- cc: Array(mail.cc),
76
- bcc: Array(mail.bcc),
76
+ to: named(mail, :to),
77
+ cc: named(mail, :cc),
78
+ bcc: named(mail, :bcc),
77
79
  reply_to: Array(mail.reply_to),
78
80
  from: mail[:from]&.value,
79
81
  subject: mail.subject.to_s.gsub(/[[:space:]]+/, " ").strip,
@@ -82,6 +84,17 @@ module Wakio
82
84
  }.compact
83
85
  end
84
86
 
87
+ # `formatted` keeps each recipient's display name, which Wakio shows in the To line and ignores
88
+ # for matching. reply_to stays bare above: Wakio takes no name there.
89
+ # A header Mail cannot parse is an UnstructuredField with no #formatted; sent as written, Wakio
90
+ # refuses it and the host sees Wakio::Rejected rather than a NoMethodError.
91
+ def named(mail, field)
92
+ header = mail[field]
93
+ return [] if header.nil?
94
+
95
+ header.respond_to?(:formatted) ? Array(header.formatted) : [ header.value.to_s ]
96
+ end
97
+
85
98
  # Parts, not `mail.body.decoded`: on a multipart message that is `""`, and multipart is
86
99
  # what every Rails mailer with both an .html.erb and a .text.erb view produces.
87
100
  def bodies_for(mail)
@@ -135,11 +148,12 @@ module Wakio
135
148
  body = parsed(raw)
136
149
  code = body["error"] if body.is_a?(Hash)
137
150
  kind =
138
- if !status.between?(400, 499) || status == 409 then DeliveryError
151
+ if status.between?(300, 399) then Misconfigured
152
+ elsif !status.between?(400, 499) || status == 409 then DeliveryError
139
153
  elsif REJECTED_STATUSES.include?(status) || REJECTED_CODES.include?(code) then Rejected
140
154
  else Misconfigured
141
155
  end
142
- kind.new("Wakio returned #{status}: #{raw}", status: status, body: body)
156
+ kind.new("Wakio returned #{status}: #{raw.to_s[0, MESSAGE_BODY_LIMIT]}", status: status, body: body)
143
157
  end
144
158
 
145
159
  def parsed(body)
@@ -154,6 +168,9 @@ module Wakio
154
168
 
155
169
  def post(payload, idempotency_key:)
156
170
  uri = URI.join(settings.fetch(:host), "/v1/emails")
171
+ unless uri.scheme == "https" || LOCAL_HOSTS.include?(uri.hostname)
172
+ raise Misconfigured, "Wakio host must be https, or the API key travels in cleartext: #{settings.fetch(:host)}"
173
+ end
157
174
  request = Net::HTTP::Post.new(uri)
158
175
  request["Authorization"] = "Bearer #{settings.fetch(:api_key)}"
159
176
  request["Content-Type"] = "application/json"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wakio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - WorkersLab LLC