clicksend 1.0.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b7857a1233302a7bc4a7056dba951907dd525d2c9fa7b1760fda426426a01f82
4
- data.tar.gz: 910dbea50624ca6652f1c88ef34fc511df31ed929352225161592122a259d648
3
+ metadata.gz: 73ecf8d49a2a4fa5ffceef490e0f6ebaeb3ccb903286f5b80f31ae82a1a22d30
4
+ data.tar.gz: 51cde5f40184bcd8b80b9bcf404076b48b17f9904eb26cb8aab781356980666c
5
5
  SHA512:
6
- metadata.gz: e2fa280f9073402b2512b9c47b7d49b42a1e7f98983b2ce077b3eb6299cf210ca5c565fc611e8dcceeed72a865dd36f231d579bd1cb28e3c9b5e04f85f7d55ff
7
- data.tar.gz: 1af0e0028c6093e4c40b6a1c083c15e1c339cf08d8ac1fe52c614919e5602c42163a20ab757ca214d02f80f74802a82c0c8b92ade79f84bca1681d7c71384e86
6
+ metadata.gz: 90b530124f465bf299cdc25acf770239f1963c9b87e96cc7a9685d8fbfc1029ec72c050e07e0882d2d7b65bc9244e74dfd36934b30c159f616b2e63151d6f086
7
+ data.tar.gz: d14f63a3e5a1ba4ee4f862b82902fcae71f079b5a1d55f06bc2b784c474e19612e37078f88f9382ad3ab48ba3f41568158b57f5fb0bd3757af5b03251395033b
data/CHANGELOG.md CHANGED
@@ -4,6 +4,126 @@ All notable changes to this project are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.1.0] - 2026-10-06
8
+
9
+ Failure semantics, observability and testing support for production messaging. Mostly additive;
10
+ read "Changed" before upgrading. Two additions are **experimental** and may change in a minor
11
+ release: `Clicksend::Webhook` and `Clicksend::RateLimit`, because both rest on behaviour ClickSend
12
+ doesn't document.
13
+
14
+ Retry safety is unchanged in principle and stricter in practice. ClickSend has no idempotency key,
15
+ so a send that may already have been processed is never repeated automatically, and such failures
16
+ are now marked as ambiguous. This reduces the risk of duplicate SMS; it is not a guarantee against
17
+ every possible duplicate (for example, a job runner re-running a job after a crash).
18
+
19
+ ### Added
20
+
21
+ - **Explicit ambiguity.** When a request that is not safe to repeat (such as an SMS send) fails
22
+ in a way that ClickSend may still have processed, the error is extended with
23
+ `Clicksend::AmbiguousRequestError`. That covers:
24
+ - a timeout or reset after the request may have been written;
25
+ - a 5xx;
26
+ - an error reported inside a 2xx body;
27
+ - a 2xx answer the gem can't read, including a send result without a per-message status.
28
+
29
+ The error keeps its class, so existing `rescue` clauses still work. `Error#ambiguous?` is the
30
+ predicate. (A `dup` of the error loses the mark; `clone` and re-raising keep it.)
31
+ - **Request context on errors and responses.** `Error#request` and `Response#request` return a
32
+ `Clicksend::RequestInfo` with `http_method`, `path` (no query string or fragment), `operation`
33
+ (e.g. `"sms.deliver"`), `idempotent` and `attempts`. `Response#request` is not a `Data`
34
+ member, so `Response` equality, `to_h` and pattern matching are unchanged from 1.0.
35
+ - `Error#retryable?`: whether repeating the same request later is both safe and might succeed.
36
+ - **Retry configuration.** `Clicksend::RetryPolicy` is public:
37
+ `Client.new(retry_policy: RetryPolicy.new(max_retries:, base_delay:, max_delay:, max_retry_after:))`.
38
+ The rule deciding *which* failures may be retried lives in the connection and cannot be
39
+ changed by any policy, and the connection enforces the policy's own `max_retries`.
40
+ - **Rate limits (experimental).** `Response#rate_limit` and `APIError#rate_limit` return a
41
+ `Clicksend::RateLimit` (`limit`, `remaining`, `reset_in`) from the rate-limit headers observed
42
+ live on `GET /v3/account`. They are nil when ClickSend sends none.
43
+ - **Instrumentation.** `Client.new(instrumenter:)` accepts `ActiveSupport::Notifications` or any
44
+ object with the same `instrument` signature that yields once. It publishes `request.clicksend`
45
+ and `retry.clicksend`. Payloads never include credentials, query strings or bodies, and the
46
+ wrapped methods' paths contain no phone numbers or message text. `Client#request` and
47
+ `#paginate` take an optional `operation:` label.
48
+ - **Message history.** `sms.history(date_from:, date_to:, to:/from:/status:/message_id:, order:)`
49
+ returns a page of `Clicksend::SMS::HistoryRecord`, whose `delivered?`, `failed?` and `pending?`
50
+ follow ClickSend's "SMS error codes" article and are all false when a row can't be classified
51
+ (e.g. "Completed" with no gateway code, as observed live). ClickSend documents no way to look up
52
+ a send by your own reference; history, filtered by recipient and matched on `custom_string`, is
53
+ the closest. The README explains why a missing row is not proof that nothing was sent.
54
+ - **Webhooks (experimental).** `Clicksend::Webhook.parse_receipt`, `.parse_inbound` and `.parse`
55
+ turn pushed receipts and replies into `SMS::Receipt` and `SMS::InboundMessage`. ClickSend
56
+ documents no way to authenticate pushes, so there is deliberately no verification method; the
57
+ README explains how to secure the endpoint. No real push has been captured yet.
58
+ - **Testing.** `require "clicksend/testing"` adds `Clicksend::Testing::FakeAPI`, an in-memory
59
+ ClickSend you plug in as the transport, covering sends, receipts, replies and the account. It
60
+ records sent messages and can inject failures, including ambiguous ones with an explicit
61
+ `processed:` flag. It deliberately doesn't serve history; stub it. Mistakes in stubs surface
62
+ as `Clicksend::Testing::StubError` (not a `StandardError`), never as a simulated ClickSend
63
+ failure.
64
+
65
+ ### Changed
66
+
67
+ Behaviour an existing 1.0 application may notice:
68
+
69
+ - **Mark-read without a cutoff is no longer retried.** `sms.mark_receipts_read` and
70
+ `sms.mark_inbound_read` with no `before:` mark *everything* read at the moment ClickSend
71
+ processes the call. Retrying after an unknown outcome could hide items that arrived in between.
72
+ With `before:` they are still retried.
73
+ - **Error messages end with the request they came from**, e.g. `HTTP 500 (POST /v3/sms/send)`.
74
+ Code that matched the exact message text needs updating. `MessageRejected` now carries
75
+ `#request` too.
76
+ - **Unreadable 2xx answers to sends are ambiguous.** Before, they were plain
77
+ `MalformedResponseError`s. That covers `deliver` and `deliver_batch`, including a message
78
+ without a status, which 1.0 reported as a rejection (`MessageRejected` with a nil status, or
79
+ in `Batch#rejected`).
80
+ - **Custom transports:**
81
+ - An exception that is not a `Clicksend::Error` becomes a `ConnectionError` that may have been
82
+ sent. It is ambiguous for a send, retried for idempotent requests, and keeps the original as
83
+ `#cause`; `rescue MyTransportError` around client calls no longer matches.
84
+ - Any other `Clicksend::Error` raised by a transport is treated as an unknown outcome.
85
+ - A response without a valid HTTP status, or an unexpected 1xx/3xx, is ambiguous for a send
86
+ instead of being a rejection.
87
+ - Exceptions raised by transports are copied before context is added.
88
+ - **Loggers and instrumenters cannot change a result.** A logger or instrumenter that raises
89
+ after a request completes is logged and ignored. An instrumenter that never runs the block
90
+ raises `ConfigurationError`, and one that runs it twice can't send twice.
91
+ - **Configuration:**
92
+ - `Client.new(max_retries:)` and `retry_policy:` are mutually exclusive. `Client#with` replaces
93
+ one with the other, and `max_retries: nil` now means the default.
94
+ - `RetryPolicy.new` validates its arguments (`ConfigurationError`) and returns a frozen policy.
95
+ - `Client#request(idempotent:)` honours only `true`; other truthy values, such as `1` or
96
+ `"false"`, no longer make a request retryable.
97
+
98
+ ### Compatibility
99
+
100
+ - Ruby 3.3 or newer, as before; tested on 3.3, 3.4 and 4.0, with a Ruby head canary in CI.
101
+ - Faraday 2 remains the only runtime dependency. ActiveSupport is used only in this gem's own
102
+ tests; `instrumenter:` duck-types it.
103
+ - No public method or class from 1.0 was removed. `require "clicksend/testing"` is opt-in and
104
+ not loaded by `require "clicksend"`.
105
+
106
+ ### Verification
107
+
108
+ - Unit, integration (local real-socket servers) and contract specs cover the new behaviour.
109
+ Contract specs check the wrapped operations, the fields the models read, and the documented
110
+ page-size range against ClickSend's published OpenAPI files.
111
+ - Live checks on 2026-10-06 were read-only: `sms.history` filtered to ClickSend's test number,
112
+ and the rate-limit headers on `GET /v3/account`. No SMS was sent.
113
+ - **Not verified live:** a webhook push (none has been captured), and a delivery receipt. The
114
+ test number produced none, as ClickSend's legacy docs say it won't. Both are parsed according
115
+ to ClickSend's published schemas and archived documentation.
116
+
117
+ ### Documentation
118
+
119
+ - README: positioning, unknown send outcomes and reconciliation, webhooks, history, background
120
+ jobs (including at-least-once job runners), instrumentation, and rate limits. Behaviour that
121
+ ClickSend doesn't document is labelled as inferred or observed.
122
+ - API notes: a review of all 34 of ClickSend's OpenAPI sections plus the archived push
123
+ documentation; read-only live checks of history and rate-limit headers on 2026-10-06.
124
+ - `design/1.1-audit-and-roadmap.md`: the audit, the decisions taken, the features rejected, and
125
+ the reviews.
126
+
7
127
  ## [1.0.0] - 2026-10-05
8
128
 
9
129
  No changes to the library's behaviour or public API since 1.0.0.rc1.
@@ -72,6 +192,7 @@ A rewrite for ClickSend's REST v3 API and modern Ruby. See [MIGRATING.md](MIGRAT
72
192
  - Last release of the original gem: send SMS, poll replies and delivery reports, and check
73
193
  the balance through ClickSend's v2 API.
74
194
 
195
+ [1.1.0]: https://github.com/prayantr/clicksend/compare/v1.0.0...v1.1.0
75
196
  [1.0.0]: https://github.com/prayantr/clicksend/compare/v1.0.0.rc1...v1.0.0
76
197
  [1.0.0.rc1]: https://github.com/prayantr/clicksend/compare/c99edc5...v1.0.0.rc1
77
198
  [0.0.3]: https://github.com/prayantr/clicksend/tree/c99edc5