wakio 0.1.0 → 0.1.2

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: 2b058707e5d5a45ba09f1ab08a918b13cd24421ff6090c957ac42e8f7ef2ddb3
4
- data.tar.gz: 90c415eb9823dfca80567242d5402d3d9a61cc3636d6b8e12ab768463c1851c0
3
+ metadata.gz: 701b8f414dc39686af35c768fd62f176365f50e8e10cf5fc8d47fe8473fad702
4
+ data.tar.gz: ab412723cc02b1ce9e04c9fdcb51b0282f467d1f43dacbd415a4df4532111406
5
5
  SHA512:
6
- metadata.gz: c011065c01a89cdffab9684e0aa88a175f657e601246c0780aa193f85b2cf900b541b1709d20e454f011b7f5876243e11cb55c29354c961e3ca628abca5baa22
7
- data.tar.gz: f53a850d624b30ccfb86a0525b88de4946d1534bd7cb2d053f983d1faf66cd70256045d4d42d4f7944272cce1479606e57235120263b04d1ecdba273b0cf1bc8
6
+ metadata.gz: 1e5191bf7700df69a86c119fd2dadbc4e8ef26e5530b68dd60727057f6f9c08e6e6e1928040001986585dd9ebf4ea9f9032da6561687330dff4f887c654aea85
7
+ data.tar.gz: 20c01bf3d406b7431632aac6367ecb227fc735fef780cba4f4c760db1df898fbc34b5def97e0c46f96ed1742b9e5c575666b180845300892e8fe423566cb16ea
data/README.md CHANGED
@@ -14,6 +14,8 @@ 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.
18
+
17
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.
18
20
 
19
21
  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,6 +24,24 @@ The `from` domain must be one of your account's sendable sending domains, matche
22
24
 
23
25
  **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.
24
26
 
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`.
28
+
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.
32
+
33
+ ```ruby
34
+ class ApplicationMailer < ActionMailer::Base
35
+ rescue_from Wakio::Suppressed do |error|
36
+ Rails.logger.info(error.message)
37
+ end
38
+
39
+ rescue_from Wakio::Rejected do |error|
40
+ Rails.error.report(error)
41
+ end
42
+ end
43
+ ```
44
+
25
45
  **Fifty recipients per request.** `to`, `cc` and `bcc` are counted together, once
26
46
  case-insensitive duplicates collapse, against Wakio's own contract limit — and the
27
47
  whole request is refused past it rather than truncated. The number mirrors SES's own
@@ -3,8 +3,20 @@ require "net/http"
3
3
  require "uri"
4
4
 
5
5
  module Wakio
6
- DeliveryError = Class.new(StandardError)
6
+ class DeliveryError < StandardError
7
+ attr_reader :status, :code, :body
8
+
9
+ def initialize(message = nil, status: nil, body: nil)
10
+ super(message)
11
+ @status = status
12
+ @body = body
13
+ @code = body["error"] if body.is_a?(Hash)
14
+ end
15
+ end
16
+
7
17
  Suppressed = Class.new(DeliveryError)
18
+ Rejected = Class.new(DeliveryError)
19
+ Misconfigured = Class.new(DeliveryError)
8
20
 
9
21
  # An ActionMailer delivery method, so a host app keeps every existing mailer
10
22
  # untouched and changes only configuration.
@@ -20,6 +32,12 @@ module Wakio
20
32
  # obviously ours if it ever leaks into a message dump.
21
33
  IDEMPOTENCY_HEADER = "X-Wakio-Idempotency-Key".freeze
22
34
 
35
+ # Refusals about this message itself, which no retry can fix. Every other 4xx is about the
36
+ # account or its configuration and sends once an operator fixes it, so an unknown code fails
37
+ # towards retrying rather than towards dropping mail.
38
+ REJECTED_STATUSES = [ 400, 413 ].freeze
39
+ REJECTED_CODES = %w[invalid_request idempotency_key_reused].freeze
40
+
23
41
  def deliver!(mail)
24
42
  # Caller-supplied, never derived — and no key at all when the caller gives none.
25
43
  #
@@ -35,9 +53,8 @@ module Wakio
35
53
  # the caller can distinguish "this is a retry of one send" from "this is a
36
54
  # deliberate repeat", because the difference is intent rather than data.
37
55
  response = post(payload_for(mail), idempotency_key: mail[IDEMPOTENCY_HEADER]&.to_s)
38
- unless response.code.to_i.between?(200, 299)
39
- raise DeliveryError, "Wakio returned #{response.code}: #{response.body}"
40
- end
56
+ status = response.code.to_i
57
+ raise refusal(status, response.body) unless status.between?(200, 299)
41
58
 
42
59
  # A separate check from the status above, deliberately: `accepted_any?` only ever
43
60
  # returns true or raises today, but joining the two into one `&&` condition would
@@ -114,6 +131,23 @@ module Wakio
114
131
  unreadable!(response)
115
132
  end
116
133
 
134
+ def refusal(status, raw)
135
+ body = parsed(raw)
136
+ code = body["error"] if body.is_a?(Hash)
137
+ kind =
138
+ if !status.between?(400, 499) || status == 409 then DeliveryError
139
+ elsif REJECTED_STATUSES.include?(status) || REJECTED_CODES.include?(code) then Rejected
140
+ else Misconfigured
141
+ end
142
+ kind.new("Wakio returned #{status}: #{raw}", status: status, body: body)
143
+ end
144
+
145
+ def parsed(body)
146
+ JSON.parse(body.to_s)
147
+ rescue JSON::ParserError
148
+ nil
149
+ end
150
+
117
151
  def unreadable!(response)
118
152
  raise DeliveryError, "Wakio returned an unreadable body: #{response.body}"
119
153
  end
@@ -128,7 +162,8 @@ module Wakio
128
162
  request["Idempotency-Key"] = idempotency_key unless idempotency_key.nil? || idempotency_key.empty?
129
163
  request.body = payload.to_json
130
164
 
131
- Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
165
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https",
166
+ open_timeout: settings.fetch(:open_timeout, 5), read_timeout: settings.fetch(:read_timeout, 10)) do |http|
132
167
  http.request(request)
133
168
  end
134
169
  end
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.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - WorkersLab LLC