standard_circuit 0.4.1 → 0.4.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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +1 -1
- data/lib/standard_circuit/mailer/retry.rb +25 -2
- data/lib/standard_circuit/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 27549729eb52395cef12a0e01e39d48f7218b2cd564f823f2d12d515ffe2fb82
|
|
4
|
+
data.tar.gz: 6ed8b33e22ccd8e655bb75dcb815098bb4a9dd16ed1daef7bd7c9c586eb1a18d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e8ebb089c834961f250167f316c9e0648fa51f3600a6a1d6774731acdad3842be7c7fde2afb24806fb4be6af4fb341f0a412090a4100151226c9b3dca58aab46
|
|
7
|
+
data.tar.gz: 3a5f2af8401788aba1f13321a16dd23d7a94f9becf14834e6bc95672a4d21beeaa0e2b87e6cf440ad3417d90ded2c47b30067d4d3cb72634a63b1aca474a0348
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.2] - 2026-09-24
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Exhausted mail jobs now fail (dead-letter) instead of being discarded.** `mailer_retry` installs `retry_on CircuitOpenError` with a block, and ActiveJob swallows the error when that block returns normally. So on exhaustion 0.4.0 and 0.4.1 logged, sent the Sentry event, emitted `standard_circuit.mailer.retries_exhausted`, and then let the job complete. The email was silently dropped, and nothing reached Solid Queue's failed executions, where it could have been retried. The handler now reports exactly as before and then re-raises the `CircuitOpenError`, so the job fails. Earlier attempts still re-enqueue with the configured wait. If reporting itself raises, that is logged and the original error is still re-raised. No config change.
|
|
14
|
+
|
|
15
|
+
### Upgrade notes
|
|
16
|
+
- **Expect one more Sentry event per exhausted email** if your host captures failed jobs (sentry-rails' ActiveJob integration does). It is the re-raised `CircuitOpenError`, grouped by exception class rather than by the `standard_circuit-mailer-retries-exhausted` fingerprint. The fingerprinted event is unchanged and is still the one to alert on. To keep only the fingerprinted event, drop the job-failure event in `before_send`. The gem does not set the fingerprint on the ambient Sentry scope, because outside a job's own scope (inline `perform_now`) it would leak onto unrelated events.
|
|
17
|
+
- Failed mail jobs now accumulate in your queue backend's failed jobs (Solid Queue: `solid_queue_failed_executions`). Retry them once the provider is healthy, or discard them deliberately.
|
|
18
|
+
|
|
10
19
|
## [0.4.1] - 2026-09-24
|
|
11
20
|
|
|
12
21
|
### Fixed
|
data/README.md
CHANGED
|
@@ -219,7 +219,7 @@ end
|
|
|
219
219
|
- **Keep `wait` longer than the mail circuit's `cool_off_time`** (the `:postmark` preset uses 60s), or retries land on a circuit that is still open.
|
|
220
220
|
- **Reload-safe and idempotent.** Calling `configure` from `to_prepare` on every reload installs the handler once. If a `retry_on CircuitOpenError` is already on the job (for example your old initializer, during migration), the gem leaves it alone. The first install wins, so changing the options later needs a restart.
|
|
221
221
|
- **Subclasses inherit it.** A custom `self.delivery_job = MyJob < ActionMailer::MailDeliveryJob` gets the handler, and a `retry_on CircuitOpenError` declared on the subclass takes precedence.
|
|
222
|
-
- **On exhaustion** it writes one `error` log line, sends a Sentry `:error` event (when `sentry_enabled` and Sentry is initialized, fingerprinted by mailer and action), and emits `standard_circuit.mailer.retries_exhausted`. Every payload holds only `mailer_class`, `mail_action`, `recipient_domains`, `job_id` and `executions`: recipient **domains**, never addresses or subjects.
|
|
222
|
+
- **On exhaustion** it writes one `error` log line, sends a Sentry `:error` event (when `sentry_enabled` and Sentry is initialized, fingerprinted by mailer and action), and emits `standard_circuit.mailer.retries_exhausted`. Every payload holds only `mailer_class`, `mail_action`, `recipient_domains`, `job_id` and `executions`: recipient **domains**, never addresses or subjects. It then **re-raises** the `CircuitOpenError`, so the job fails and lands in your queue backend's failed jobs (Solid Queue's failed executions) where it can be inspected and retried, instead of completing with the email dropped (since 0.4.2). If your Sentry setup also captures job failures (sentry-rails' ActiveJob integration does), that failure arrives as a second event, grouped by the exception rather than by the fingerprint above.
|
|
223
223
|
- Only `CircuitOpenError` is retried. Errors from the provider while the circuit is closed, such as a SendGrid 429 or a Postmark 422, keep their existing behaviour. Add your own `retry_on` / `discard_on` for those.
|
|
224
224
|
|
|
225
225
|
Replace your host code with:
|
|
@@ -29,7 +29,9 @@ module StandardCircuit
|
|
|
29
29
|
# On exhaustion: one error log line, a Sentry `:error` event (when
|
|
30
30
|
# `sentry_enabled` and Sentry is initialized), and a
|
|
31
31
|
# `standard_circuit.mailer.retries_exhausted` event. PII: recipient
|
|
32
|
-
# DOMAINS only — never addresses or subjects.
|
|
32
|
+
# DOMAINS only — never addresses or subjects. Then the CircuitOpenError is
|
|
33
|
+
# re-raised, so the job FAILS (dead letter) rather than completing with
|
|
34
|
+
# the email dropped (0.4.2; 0.4.0/0.4.1 swallowed it).
|
|
33
35
|
module Retry
|
|
34
36
|
EXHAUSTED_EVENT = "standard_circuit.mailer.retries_exhausted".freeze
|
|
35
37
|
EXHAUSTED_MESSAGE = "Email delivery failed: mail circuit breaker retries exhausted".freeze
|
|
@@ -115,11 +117,32 @@ module StandardCircuit
|
|
|
115
117
|
attempts: options.fetch(:attempts),
|
|
116
118
|
jitter: options.fetch(:jitter)
|
|
117
119
|
) do |job, error|
|
|
118
|
-
StandardCircuit::Mailer::Retry.
|
|
120
|
+
StandardCircuit::Mailer::Retry.exhausted!(job, error)
|
|
119
121
|
end
|
|
120
122
|
true
|
|
121
123
|
end
|
|
122
124
|
|
|
125
|
+
# Runs when the last attempt still hits an open circuit. Reports first
|
|
126
|
+
# (log, fingerprinted Sentry event, retries_exhausted event), then
|
|
127
|
+
# RE-RAISES so the job fails. ActiveJob's retry_on swallows the error
|
|
128
|
+
# when given a block unless the block raises, and a swallowed error
|
|
129
|
+
# means the job "succeeds" and the email is silently dropped. Raising
|
|
130
|
+
# lands it in the queue backend's failed executions (Solid Queue's
|
|
131
|
+
# dead letter), where it can be inspected and retried.
|
|
132
|
+
#
|
|
133
|
+
# A report that raises must not turn into a dropped email, so it is
|
|
134
|
+
# rescued and logged; the original error is raised regardless.
|
|
135
|
+
#
|
|
136
|
+
# @api private
|
|
137
|
+
def exhausted!(job, error)
|
|
138
|
+
begin
|
|
139
|
+
report_exhausted(job, error)
|
|
140
|
+
rescue StandardError => report_error
|
|
141
|
+
logger&.error("[standard_circuit] failed to report mail retry exhaustion: #{report_error.class}: #{report_error.message}")
|
|
142
|
+
end
|
|
143
|
+
raise error
|
|
144
|
+
end
|
|
145
|
+
|
|
123
146
|
def installed?(job_class)
|
|
124
147
|
job_class.rescue_handlers.any? { |(error_name, _handler)| error_name == CircuitOpenError.name }
|
|
125
148
|
end
|