unisender-ruby 0.1.1 → 0.2.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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -0
  3. data/CONTRIBUTING.md +10 -1
  4. data/README.md +55 -112
  5. data/docs/api.md +395 -210
  6. data/docs/execution.md +89 -0
  7. data/docs/import-recovery.md +41 -0
  8. data/docs/live-contracts.md +34 -0
  9. data/docs/migration-0.2.md +129 -0
  10. data/docs/rails.md +248 -0
  11. data/docs/releases.md +4 -4
  12. data/docs/testing.md +83 -0
  13. data/docs/usage.md +144 -0
  14. data/docs/webhooks.md +41 -0
  15. data/examples/campaign.rb +5 -6
  16. data/examples/campaign_lifecycle.rb +28 -0
  17. data/examples/check_connection.rb +1 -1
  18. data/examples/events.rb +1 -1
  19. data/examples/export.rb +20 -6
  20. data/examples/import.rb +3 -2
  21. data/examples/import_recovery.rb +70 -0
  22. data/examples/quickstart.rb +1 -1
  23. data/examples/subscribe.rb +2 -2
  24. data/lib/unisender/acceptance.rb +104 -0
  25. data/lib/unisender/attachment.rb +47 -0
  26. data/lib/unisender/batch_report.rb +115 -0
  27. data/lib/unisender/batch_summary.rb +87 -0
  28. data/lib/unisender/batches.rb +41 -12
  29. data/lib/unisender/campaign_workflow.rb +72 -0
  30. data/lib/unisender/client.rb +65 -33
  31. data/lib/unisender/client_configuration.rb +50 -0
  32. data/lib/unisender/client_execution.rb +43 -0
  33. data/lib/unisender/client_state.rb +31 -0
  34. data/lib/unisender/client_validation.rb +97 -0
  35. data/lib/unisender/configuration.rb +151 -0
  36. data/lib/unisender/contact_records.rb +251 -0
  37. data/lib/unisender/diagnostic_urls.rb +68 -0
  38. data/lib/unisender/diagnostics.rb +83 -0
  39. data/lib/unisender/download_target.rb +61 -0
  40. data/lib/unisender/download_transfer.rb +43 -0
  41. data/lib/unisender/downloader.rb +99 -0
  42. data/lib/unisender/error_diagnostics.rb +83 -0
  43. data/lib/unisender/errors.rb +125 -8
  44. data/lib/unisender/event_batches.rb +40 -0
  45. data/lib/unisender/event_payload.rb +50 -22
  46. data/lib/unisender/events.rb +21 -5
  47. data/lib/unisender/execution_context.rb +114 -0
  48. data/lib/unisender/form.rb +20 -12
  49. data/lib/unisender/helpers.rb +70 -10
  50. data/lib/unisender/hooks.rb +79 -0
  51. data/lib/unisender/import_cells.rb +62 -0
  52. data/lib/unisender/input_types.rb +30 -0
  53. data/lib/unisender/inputs.rb +262 -0
  54. data/lib/unisender/iteration.rb +15 -0
  55. data/lib/unisender/iterators.rb +60 -0
  56. data/lib/unisender/mail_inputs.rb +37 -0
  57. data/lib/unisender/named_inputs.rb +52 -0
  58. data/lib/unisender/observation.rb +30 -0
  59. data/lib/unisender/operation_contracts.rb +106 -0
  60. data/lib/unisender/operations/contacts_1.rb +4 -3
  61. data/lib/unisender/operations/contacts_2.rb +23 -4
  62. data/lib/unisender/operations/messages_1.rb +7 -4
  63. data/lib/unisender/operations/messages_2.rb +8 -3
  64. data/lib/unisender/operations/messages_3.rb +1 -1
  65. data/lib/unisender/operations/notes_1.rb +1 -1
  66. data/lib/unisender/operations/statistics_1.rb +3 -3
  67. data/lib/unisender/operations/templates_1.rb +2 -2
  68. data/lib/unisender/outcomes.rb +320 -0
  69. data/lib/unisender/page_response.rb +48 -0
  70. data/lib/unisender/paginated_operations.rb +43 -2
  71. data/lib/unisender/pagination.rb +53 -20
  72. data/lib/unisender/persistent_transport.rb +104 -0
  73. data/lib/unisender/rate_limiter.rb +72 -0
  74. data/lib/unisender/record.rb +210 -0
  75. data/lib/unisender/record_rows.rb +69 -0
  76. data/lib/unisender/records.rb +614 -0
  77. data/lib/unisender/request_execution.rb +108 -0
  78. data/lib/unisender/response.rb +13 -10
  79. data/lib/unisender/result_registry.rb +72 -0
  80. data/lib/unisender/results.rb +105 -0
  81. data/lib/unisender/retry_policy.rb +10 -16
  82. data/lib/unisender/serialization.rb +54 -0
  83. data/lib/unisender/statistic_records.rb +129 -0
  84. data/lib/unisender/task_response.rb +85 -0
  85. data/lib/unisender/task_waiter.rb +25 -11
  86. data/lib/unisender/testing/assertions.rb +48 -0
  87. data/lib/unisender/testing/fixtures.rb +24 -0
  88. data/lib/unisender/testing.rb +91 -0
  89. data/lib/unisender/transport.rb +17 -9
  90. data/lib/unisender/validation.rb +11 -9
  91. data/lib/unisender/version.rb +1 -1
  92. data/lib/unisender/webhooks/tokens.rb +38 -0
  93. data/lib/unisender/webhooks.rb +230 -0
  94. data/lib/unisender.rb +42 -0
  95. data/sig/acceptance.rbs +30 -0
  96. data/sig/attachment.rbs +15 -0
  97. data/sig/batch_summary.rbs +38 -0
  98. data/sig/campaign_workflow.rbs +20 -0
  99. data/sig/configuration.rbs +62 -0
  100. data/sig/connection_controls.rbs +55 -0
  101. data/sig/diagnostics.rbs +37 -0
  102. data/sig/domain_records.rbs +96 -0
  103. data/sig/downloads.rbs +53 -0
  104. data/sig/execution.rbs +52 -0
  105. data/sig/execution_context.rbs +46 -0
  106. data/sig/hooks.rbs +18 -0
  107. data/sig/inputs.rbs +84 -0
  108. data/sig/iterators.rbs +11 -0
  109. data/sig/mail_inputs.rbs +8 -0
  110. data/sig/manifest.yaml +11 -0
  111. data/sig/operation_contracts.rbs +28 -0
  112. data/sig/operations.rbs +73 -69
  113. data/sig/outcomes.rbs +90 -0
  114. data/sig/record.rbs +77 -0
  115. data/sig/records.rbs +182 -0
  116. data/sig/serialization.rbs +13 -0
  117. data/sig/tasks.rbs +26 -0
  118. data/sig/testing.rbs +54 -0
  119. data/sig/unisender.rbs +128 -39
  120. data/sig/webhooks.rbs +55 -0
  121. metadata +84 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d06d19534449d52a259839973e260a8cd7e11cbc50c8136bf963b47c06f86624
4
- data.tar.gz: b0de90f56f2838c27f6a1b3eea28654d45209cc4e3446b6f401ce6f31f005d35
3
+ metadata.gz: f3678167b25f05feee7a6cbb24ead0d09fea765de2cbd815f092644c1ee62598
4
+ data.tar.gz: c0d66eec6a7c5ea4b93e162a7c4ebc62c9548585c6e4cf3cc5f49a50d345f0b5
5
5
  SHA512:
6
- metadata.gz: 44c26c949db6f70ef169a21b2e2c523640cb3ead8d4b551aa6412dea828bf3bee6c874e162b3cfb3292534721d14b4d1ec114bfd58c4433a07e99f5e496dddbd
7
- data.tar.gz: 5d554001273bfd9562c6968fcffe3f1b98c6706093dae5bcfee8f890ad08fb75486cafb418956776c976af581c41ddd8cb7f9f4c2ae29502e17624923c468816
6
+ metadata.gz: 8c15b5e69db9bb9584e75c52a1666ed99cf5cae211be9acf9b154943068e1f168803d332b2e907ec8ab4dccbec1ad7faceb3685a7006eaad4e97bb70b3ca5c8e
7
+ data.tar.gz: a634d99895b18c0df41f1e5b01f1943ca15f56e9921624be600c0be5b0eaf9b6e5e8745b7cb2d5e20c521127b679f8e0190a1fcf56dee239a788d96157e06a49
data/CHANGELOG.md CHANGED
@@ -4,6 +4,95 @@ Unreleased collects changes for the next version. Dated sections list user-visib
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.2.0 - 2026-10-26
8
+
9
+ - Parse diagnostic bracket fields without regex substitution while preserving index paths and redaction.
10
+
11
+ - Add campaign lifecycle and durable import examples, a complete Rails integration, verified Cursor setup, and shorter task-oriented documentation.
12
+
13
+ - Keep offline pagination validation aligned with execution and report indexed event and response-shape errors.
14
+ - Mark acceptance as uncertain when responses omit inputs or return inconsistent counts; enforce false JSON expectations.
15
+
16
+ - Add optional fixture builders and redacted subset assertions for queued form and JSON requests.
17
+
18
+ - Add optional acceptance instrumentation with separate accepted, rejected, and unknown counts for sends, imports, and events.
19
+
20
+ - Identify API requests with the gem and Ruby versions and document client ownership across variants, threads, and forks.
21
+
22
+ - Add redacted error diagnostics, structural field paths, expected and actual types, and request context on malformed responses.
23
+
24
+ - Add an independent optional Event Tracking rate limiter and validate message-default containers locally.
25
+
26
+ - Add named mail headers, image-mode symbols, and explicit accepted, rejected, and unknown recipient views.
27
+
28
+ - Accept SMS phone arrays and always return a typed receipt collection.
29
+
30
+ - Give records class-sensitive value equality, forward object lookup, and avoid polling failed task handles.
31
+
32
+ - Honor top-level `only` and `except` JSON filters and reject unsupported serialization options.
33
+
34
+ - Validate named argument types locally and expose offline `Client#validate!` with shared normalization and encoding.
35
+
36
+ - Bound direct imports to 501 inspected records and add optional byte limits to attachment reads.
37
+
38
+ - Schedule Time inputs in explicit UTC with minute precision; reject conflicting timezones.
39
+
40
+ - Document the accepted MD5 exception required by UniSender's webhook protocol.
41
+
42
+ - Scan webhook JSON tokens without regular-expression backtracking and reject unterminated strings.
43
+ - Escape backslashes as well as table separators in generated API type descriptions.
44
+
45
+ - Preserve existing injected retry policies, rate limiters, and downloaders when execution scopes are enabled.
46
+ - Add cooperative execution scopes and cancellation across retries, pacing, polling, and batch boundaries, with separate execution timing events and request IDs on request events.
47
+ - Add opt-in summary retention and progress checkpoints for import and Event Tracking batches, preserving known source offsets for row diagnostics.
48
+ - Add hook management and bounded JSON/gzip webhook authentication with typed notification readers.
49
+ - Add fake-transport queue verification and JSON request access, durable export recovery examples, and opt-in staging contract probes.
50
+
51
+ - Validate malformed contact fields and count filters locally, and preserve operation names and import column paths in validation errors.
52
+ - Correct reference descriptions of legacy empty-result normalization.
53
+
54
+ - Standardize input diagnostics, validate named flags and choices, and preserve wrapper failure context.
55
+ - Normalize structured import cells and expose typed row warnings with known global indices.
56
+ - Add scoped email/phone removal inputs and explicit contact-count filters.
57
+ - Request detailed send acceptance by default; retain explicit legacy and omission options.
58
+
59
+ - Accept blocks on page iterators and expose page checkpoint offsets.
60
+ - Add Array-style collection access, Hash-style record lookup, and payload JSON serialization.
61
+ - Add stable contact address readers, optional contact details, note metadata, and mutation result readers.
62
+ - Return completed task handles without polling; expose task predicates and block-scoped client lifetime.
63
+
64
+ - Separate development installation from published usage and link gem documentation to its version tag.
65
+ - Generate API argument and result tables from RBS, with agreement checks for the Ruby interface.
66
+
67
+ - Expose all documented import counters directly and normalize string-keyed default overrides.
68
+ - Validate campaign encoding before creating a message.
69
+
70
+ - Preserve accepted batches on source exceptions and task context on polling request failures.
71
+ - Validate direct pagination controls and include named-result failures in instrumentation.
72
+ - Reject special-use download addresses and complete Time signatures for iterators.
73
+
74
+ - Add opt-in persistent HTTPS connections, lifecycle controls, and shared process-local request pacing.
75
+ - Add binary attachment snapshots and a preflighted create-message/send-campaign workflow.
76
+ - Add explicit, bounded export downloads without forwarding API credentials.
77
+
78
+ - Add direct timeout and retry options, environment construction, immutable variants, and message defaults.
79
+ - Add safe request instrumentation, structured error categories, recovery hints, and retry-safety checks.
80
+ - Ship optional test transport support and application integration guides.
81
+ - Expand typed contact, sender-domain, SMS, and campaign-statistics results.
82
+
83
+ - Expose typed acceptance reports and global batch failure indices without replaying requests.
84
+ - Split event batches by encoded bytes and count; preserve completed work when input enumeration fails.
85
+
86
+ - Accept contact records, named subscription modes, and Ruby time inputs.
87
+ - Add task handles with explicit polling, progress callbacks, and contextual timeout errors.
88
+
89
+ - Execute batch commands immediately; provide explicit lazy batch and typed item iterators.
90
+ - Add page navigation with resume offsets and immediate argument validation.
91
+
92
+ - Return typed immutable records and enumerable collections from named operations.
93
+ - Add direct field access, a boolean membership predicate, and singular message results.
94
+ - Rename response HTTP metadata to `http_status`; preserve upstream JSON through `data`.
95
+
7
96
  ## 0.1.1 - 2026-10-25
8
97
 
9
98
  - Clarify installation, example destinations, contribution guidance, and maintainer policies.
data/CONTRIBUTING.md CHANGED
@@ -39,9 +39,18 @@ Pre-commit hooks scan staged changes for secrets, apply and stage safe Ruby form
39
39
 
40
40
  ## Build and test the package
41
41
 
42
+ API reference tables come from shipped RBS signatures and `docs/api_annotations.yml`.
43
+ After changing either, regenerate the tables:
44
+
45
+ ```sh
46
+ bundle exec ruby -r ./scripts/api_reference -e 'File.write("docs/api.md", APIReference.new(Dir.pwd).render)'
47
+ ```
48
+
49
+ The regular test suite checks that generated tables match the public Ruby keywords.
50
+
42
51
  ```sh
43
52
  bundle exec rake build
44
- gem install --local ./pkg/unisender-ruby-0.1.1.gem
53
+ ruby -e 'spec = Gem::Specification.load("unisender.gemspec"); system("gem", "install", "--local", "pkg/#{spec.file_name}") || abort("Install failed")'
45
54
  ```
46
55
 
47
56
  From outside the checkout, run `ruby -runisender -e 'puts Unisender::VERSION'`. For an isolated check, use a temporary `GEM_HOME` and `GEM_PATH`, and run the examples with a fake transport. The gem includes examples and RBS signatures; development dependencies are not runtime dependencies.
data/README.md CHANGED
@@ -2,71 +2,50 @@
2
2
 
3
3
  A Ruby client for applications using the [UniSender marketing API](https://www.unisender.com/ru/support/category/api/).
4
4
 
5
- Ruby 3.3 or newer, no runtime gem dependencies. This client targets `api.unisender.com`, not UniSender Go.
6
-
7
- Explicit keyword methods cover contacts, lists, messages, campaigns, statistics, templates, fields, notes, and Event Tracking. Responses preserve upstream JSON, warnings, and HTTP metadata. See the [API reference](docs/api.md) for the operation index, argument types, and result shapes.
5
+ - Ruby 3.3 or newer, with no runtime gem dependencies.
6
+ - Contacts, lists, messages, campaigns, statistics, templates, fields, notes, and Event Tracking.
7
+ - Typed immutable results, offline validation, and explicit pagination and batch recovery.
8
+ - Targets `api.unisender.com` and Event Tracking, not UniSender Go.
8
9
 
9
10
  ## Install
10
11
 
11
- Install the gem:
12
-
13
- ```sh
14
- gem install unisender-ruby
15
- ```
12
+ This guide covers version 0.2. See the [migration guide](docs/migration-0.2.md) when upgrading from 0.1.
16
13
 
17
- Save this read-only example as `quickstart.rb`. It uses the key in `UNISENDER_API_KEY`:
14
+ Add the gem to your application's `Gemfile`:
18
15
 
19
- <!-- example: examples/quickstart.rb -->
20
16
  ```ruby
21
- require "unisender"
22
-
23
- client = Unisender::Client.new(api_key: ENV.fetch("UNISENDER_API_KEY"))
24
- client.get_lists.result.each { |list| puts list.fetch("title") }
25
- # Example output for a list named News: News
17
+ gem "unisender-ruby", "~> 0.2.0", require: "unisender"
26
18
  ```
27
19
 
28
- Run `ruby quickstart.rb`. It prints one title per list, or nothing if the account has no lists. See [Make your first request](#make-your-first-request) for key setup.
29
-
30
- For a Bundler project, add the gem to your application's Gemfile:
31
-
32
- ```ruby
33
- gem "unisender-ruby", "~> 0.1.1", require: "unisender"
34
- ```
20
+ Run `bundle install`.
21
+ To build from a checkout, follow [build and installation](CONTRIBUTING.md#build-and-test-the-package).
22
+ The package name is `unisender-ruby`; the Ruby load path is `unisender`.
35
23
 
36
- Then run `bundle install`. The package is named `unisender-ruby`; load it with `require "unisender"`.
24
+ ## Read your lists
37
25
 
38
- ### Install from a checkout
26
+ Get your key from [UniSender's API settings](https://www.unisender.com/ru/support/api/common/api-key/).
27
+ Set `UNISENDER_API_KEY` through your shell or application's secret store.
28
+ The library does not load `.env` files. Keep credentials out of source and logs.
39
29
 
40
- In your application's Gemfile, point to this repository:
30
+ Save this as `quickstart.rb`:
41
31
 
32
+ <!-- example: examples/quickstart.rb -->
42
33
  ```ruby
43
- gem "unisender-ruby", path: "../unisender-ruby", require: "unisender"
44
- ```
45
-
46
- Adjust the path, then run `bundle install`. Alternatively, build and install the local package as described in [CONTRIBUTING.md](CONTRIBUTING.md#build-and-test-the-package).
47
-
48
- ## Make your first request
49
-
50
- Get your key from [UniSender's API settings](https://www.unisender.com/ru/support/api/common/api-key/) and put it in the `UNISENDER_API_KEY` environment variable through your shell or application's secret store. Keep it out of source control and logs.
51
-
52
- For a local checkout, you can store `UNISENDER_API_KEY=your-key` in `.env`, which Git ignores. Ruby does not load this file automatically. Load your own `.env` into the current shell, then run the read-only connection check:
34
+ require "unisender"
53
35
 
54
- ```sh
55
- set -a
56
- . ./.env
57
- set +a
58
- bundle exec ruby examples/check_connection.rb
36
+ client = Unisender::Client.new(api_key: ENV.fetch("UNISENDER_API_KEY"))
37
+ client.get_lists.each { |list| puts list.title }
38
+ # Example output for a list named News: News
59
39
  ```
60
40
 
61
- The check prints only the HTTP status and list count, including zero for an empty account. Failures exit with status 1. It does not print list names or IDs. The gem itself reads neither environment variables nor `.env`; application code passes the key explicitly.
62
-
63
- The `quickstart.rb` example above only reads lists. It does not create contacts or send messages. In a Bundler project, run it with `bundle exec ruby quickstart.rb`. A missing environment variable raises `KeyError` before any request. To try the client without an account or network access, run `bundle exec ruby examples/offline.rb` from this checkout.
41
+ Run `bundle exec ruby quickstart.rb`. An empty account prints nothing.
42
+ To try the client without credentials, run `bundle exec ruby examples/offline.rb` from the checkout.
64
43
 
65
44
  ## Subscribe a contact
66
45
 
67
- This operation changes your account and normally **sends a confirmation email**. Use an existing list ID from `get_lists`, and an address you control for a manual trial. Set `UNISENDER_LIST_ID` and `UNISENDER_CONTACT_EMAIL` before running it.
68
-
69
- Save the following example as `subscribe.rb`:
46
+ This changes your account and normally **sends a confirmation email**.
47
+ Set `UNISENDER_LIST_ID` to an existing list and `UNISENDER_CONTACT_EMAIL` to an address you control.
48
+ Save this as `subscribe.rb`:
70
49
 
71
50
  <!-- example: examples/subscribe.rb -->
72
51
  ```ruby
@@ -75,86 +54,50 @@ require "unisender"
75
54
  client = Unisender::Client.new(api_key: ENV.fetch("UNISENDER_API_KEY"))
76
55
  response = client.subscribe(
77
56
  list_ids: [ENV.fetch("UNISENDER_LIST_ID")],
78
- fields: { email: ENV.fetch("UNISENDER_CONTACT_EMAIL") }
57
+ email: ENV.fetch("UNISENDER_CONTACT_EMAIL")
79
58
  )
80
- puts response.result.fetch("person_id")
59
+ puts response.person_id
81
60
  ```
82
61
 
83
- The contact must confirm before receiving campaigns. Use `double_optin: 3` only when you already have consent; it adds the contact without sending the confirmation email. `overwrite: 0` preserves existing fields and tags, `1` replaces them and list membership, and `2` updates supplied fields. See the [upstream subscription rules](https://www.unisender.com/ru/support/api/contacts/subscribe/).
84
-
85
- ## Complete workflows
86
-
87
- Each example is a standalone Ruby file. Its header lists required environment variables and account effects.
62
+ Run `bundle exec ruby subscribe.rb`. The contact must confirm before receiving campaigns.
63
+ Use `double_optin: :already_confirmed` only with consent already established.
64
+ To check inputs without networking, call `client.validate!(:subscribe, list_ids: [123], email: "person@example.test")`.
88
65
 
89
- | Task | Example | Effect |
90
- | --- | --- | --- |
91
- | Read lists | [quickstart.rb](examples/quickstart.rb) | Read only |
92
- | Check credentials without printing account data | [check_connection.rb](examples/check_connection.rb) | Read only |
93
- | Test without credentials | [offline.rb](examples/offline.rb) | No network |
94
- | Subscribe a contact | [subscribe.rb](examples/subscribe.rb) | Changes contacts; sends confirmation |
95
- | Create and send a campaign | [campaign.rb](examples/campaign.rb) | Sends to the selected list |
96
- | Export contacts | [export.rb](examples/export.rb) | Starts and polls an export job |
97
- | Import batches with partial-failure handling | [import.rb](examples/import.rb) | Changes contacts |
98
- | Track events with partial-failure handling | [events.rb](examples/events.rb) | Records events; can trigger automation |
66
+ ## Handle a failure
99
67
 
100
- ## Responses and failures
101
-
102
- Every operation returns `Unisender::Response`: `result`, `warnings`, `data` (the full JSON object), `status`, and `headers`. Values are deeply immutable. JSON keys are strings; numbers are not coerced. Read or copy the data explicitly when your application needs to change it.
103
-
104
- Named methods use snake_case and reject unknown keywords. Optional `nil` values are omitted, preserving server defaults. API messages default to English; use `Client.new(api_key: ..., language: :ru)` for Russian. Message-level `lang:` is a separate setting.
105
-
106
- Use this error handler in your application's `request.rb`:
68
+ Put error handling around calls in your application:
107
69
 
108
70
  ```ruby
109
71
  begin
110
72
  client.get_lists
111
- rescue Unisender::APIError => error
112
- warn error.message
113
- # Fix credentials, permissions, or parameters using error.code.
114
- rescue Unisender::HTTPError => error
115
- warn error.message
116
- # Check error.response.status and error.response.headers.
117
- rescue Unisender::TransportError, Unisender::InvalidResponseError => error
118
- warn error.message
119
- # Check service availability before deciding whether to repeat the operation.
73
+ rescue Unisender::Error => error
74
+ warn error.diagnostics.inspect
120
75
  end
121
76
  ```
122
77
 
123
- `APIError` also covers API failures returned with HTTP 200; an accompanying result is discarded. `HTTPError` covers non-2xx statuses, including redirects. `InvalidResponseError` covers malformed successful responses. `TimeoutError` is a `TransportError`. All derive from `Unisender::Error`.
124
-
125
- Error messages identify known operations and HTTP statuses or documented API codes. Unknown wire method names and unrecognized API codes are not copied into messages. `error.operation` is available separately. Credentials, request bodies, and contact data are omitted. Explicit accessors such as `error.details`, `error.response.data`, and `response.data` can contain private information. No request logging is enabled.
126
-
127
- A successful envelope does not mean every recipient succeeded. Inspect `send_email(error_checking: 1, ...)` recipient errors and import counters. A `BatchError` provides `completed_responses`, the zero-based, end-exclusive `failed_range`, and the original `error`. Accepted changes remain applied; do not repeat the whole input automatically. The [batch example](examples/import.rb) shows how to inspect partial results.
128
-
129
- ## Timeouts, retries, and pagination
130
-
131
- In `quickstart.rb`, replace the client construction with:
132
-
133
- ```ruby
134
- client = Unisender::Client.new(
135
- api_key: ENV.fetch("UNISENDER_API_KEY"),
136
- transport: Unisender::Transport.new(open_timeout: 5, read_timeout: 60, write_timeout: 30),
137
- retry_policy: Unisender::RetryPolicy.new(max_retries: 2, max_elapsed: 60)
138
- )
139
- ```
140
-
141
- Those are the default transport timeouts in seconds. TLS certificates are verified, redirects are rejected, and calls do not share connections. **Retries default to zero.** The opt-in policy retries only explicitly identified read-only operations, at most twice, with backoff, jitter, and `Retry-After`. Sends, mutations, imports, exports, events, and unknown methods are never retried automatically. The elapsed retry budget is not a hard request deadline; in-flight requests use transport timeouts.
142
-
143
- `each_page` and batch helpers return lazy Enumerators: **consume them to make requests**. Stopping enumeration prevents subsequent requests. `each_page` yields responses, accepts a supported Ruby method name, and raises `PaginationError` at `max_pages`. `wait_for_task` polls an existing job; its deadline is checked between requests. See [helper limits and recovery behavior](docs/api.md#convenience-operations) and [upstream rate limits](https://www.unisender.com/ru/support/api/common/api-limits/).
144
-
145
- ## Low-level access and types
146
-
147
- `client.request("getLists", params: {})` accepts exact upstream names. It handles authentication, encoding, and failures, but does not perform operation-specific conversions. Comma-separated fields must already be strings; nested arrays and hashes become bracketed form fields. Credentials and JSON format cannot be overridden.
78
+ `diagnostics` omits credentials and payloads. Local input errors raise `Unisender::ValidationError`, an `ArgumentError` subclass.
79
+ A successful request can still reject recipients. Inspect `complete?`, `partial?`, and `uncertain?` on acceptance reports.
80
+ Retries default to zero. Sends and mutations are never automatically retried.
148
81
 
149
- RBS signatures ship with the gem. `Response[Result]` describes a named operation's result; the low-level method keeps the general JSON type. Signatures describe documented shapes, not runtime schema validation. Unknown fields remain accessible through `response.data`. Consumer examples are checked with Steep alongside library code.
82
+ ## Find your task
150
83
 
151
- ## Project information
84
+ | Task | Guide or example |
85
+ | --- | --- |
86
+ | Choose an operation or find a keyword | [API reference](docs/api.md) |
87
+ | Read results, configure clients, handle errors | [Usage guide](docs/usage.md) |
88
+ | Create, inspect, test, and schedule a campaign | [Campaign lifecycle](examples/campaign_lifecycle.rb) |
89
+ | Recover an import from durable checkpoints | [Import recovery](docs/import-recovery.md) |
90
+ | Export and download contacts | [Export example](examples/export.rb) |
91
+ | Record events | [Event example](examples/events.rb) |
92
+ | Test an application or configure Cursor | [Testing and editor setup](docs/testing.md) |
93
+ | Integrate Rails jobs and delivery webhooks | [Rails guide](docs/rails.md) |
94
+ | Control pacing, cancellation, and resource ownership | [Execution guide](docs/execution.md) |
95
+ | Upgrade from 0.1 | [Migration guide](docs/migration-0.2.md) |
152
96
 
153
- - [Contributing, local checks, and package verification](CONTRIBUTING.md)
154
- - [Changes and compatibility policy](CHANGELOG.md)
155
- - [Release verification](docs/releases.md)
156
- - [Source repository](https://github.com/svyatov/unisender-ruby) and [issue reports](https://github.com/svyatov/unisender-ruby/issues)
97
+ ## Help and project status
157
98
 
158
- This project is maintained by Leonid Svyatov. Use the public [issue tracker](https://github.com/svyatov/unisender-ruby/issues) for questions and defect reports. Report vulnerabilities privately through [SECURITY.md](SECURITY.md).
99
+ Leonid Svyatov maintains this project. Use the public [issue tracker](https://github.com/svyatov/unisender-ruby/issues) for questions and defects.
100
+ Report vulnerabilities privately through [SECURITY.md](SECURITY.md).
159
101
 
160
- Tests use fake credentials and deterministic responses. They do not establish compatibility with a live account. Released under the [MIT License](LICENSE.txt).
102
+ See [contributing](CONTRIBUTING.md), [changes](CHANGELOG.md), [release verification](docs/releases.md), and the [MIT license](LICENSE.txt).
103
+ Tests use fake credentials and deterministic responses. Live account compatibility requires separate [staging checks](docs/live-contracts.md).