unisender-ruby 0.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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +25 -0
  3. data/CONTRIBUTING.md +43 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +140 -0
  6. data/docs/api.md +1063 -0
  7. data/examples/campaign.rb +13 -0
  8. data/examples/check_connection.rb +16 -0
  9. data/examples/events.rb +20 -0
  10. data/examples/export.rb +11 -0
  11. data/examples/import.rb +18 -0
  12. data/examples/offline.rb +26 -0
  13. data/examples/quickstart.rb +6 -0
  14. data/examples/subscribe.rb +12 -0
  15. data/lib/unisender/batches.rb +31 -0
  16. data/lib/unisender/client.rb +59 -0
  17. data/lib/unisender/errors.rb +129 -0
  18. data/lib/unisender/event_payload.rb +63 -0
  19. data/lib/unisender/events.rb +19 -0
  20. data/lib/unisender/form.rb +67 -0
  21. data/lib/unisender/helpers.rb +28 -0
  22. data/lib/unisender/operations/contacts_1.rb +51 -0
  23. data/lib/unisender/operations/contacts_2.rb +44 -0
  24. data/lib/unisender/operations/inputs_1.rb +48 -0
  25. data/lib/unisender/operations/messages_1.rb +58 -0
  26. data/lib/unisender/operations/messages_2.rb +54 -0
  27. data/lib/unisender/operations/messages_3.rb +12 -0
  28. data/lib/unisender/operations/notes_1.rb +38 -0
  29. data/lib/unisender/operations/statistics_1.rb +48 -0
  30. data/lib/unisender/operations/statistics_2.rb +18 -0
  31. data/lib/unisender/operations/templates_1.rb +45 -0
  32. data/lib/unisender/paginated_operations.rb +42 -0
  33. data/lib/unisender/pagination.rb +68 -0
  34. data/lib/unisender/protocol.rb +46 -0
  35. data/lib/unisender/response.rb +64 -0
  36. data/lib/unisender/retry_policy.rb +82 -0
  37. data/lib/unisender/task_waiter.rb +52 -0
  38. data/lib/unisender/transport.rb +53 -0
  39. data/lib/unisender/validation.rb +72 -0
  40. data/lib/unisender/version.rb +5 -0
  41. data/lib/unisender.rb +29 -0
  42. data/sig/operations.rbs +78 -0
  43. data/sig/unisender.rbs +218 -0
  44. metadata +87 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5816a7b213b4919d38f81de3568187d4dd1a2f345413b529f3961c2fad33f187
4
+ data.tar.gz: 0c0dcea4ad0cb5f1e4a2f5d2a502562ff68d3edebe1fdc04c3c7e95675fc4a9f
5
+ SHA512:
6
+ metadata.gz: 2d6e970aa069fb618f59335fc935ec897f4513af963ab60728c8379072a88bdf06eb08103e7b67a942d283e3e0b308c3a546aa91e26d0a26ee8c1089a158789b
7
+ data.tar.gz: '09e933af3af43cd61bb7c03a9a8c852813b24f22c766e4d1446008c1bddadb4c2d24d964a919272f489ab78c78ea73e40f5e598bb24570164b3f57b342f1b474'
data/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.1.0 - 2026-10-25
6
+
7
+ - Publish as `unisender-ruby`; load with `require "unisender"` and use the `Unisender` namespace.
8
+ - Initial release of the UniSender marketing API client for Ruby 3.3 and newer, with no runtime gem dependencies.
9
+ - Add contacts, lists, messages, campaigns, statistics, templates, fields, notes, and Event Tracking operations.
10
+ - Add immutable responses, distinct API and transport errors, opt-in read-only retries, pagination, polling, and batch helpers.
11
+ - Document local `.env` loading and add a read-only connection check that prints no account data.
12
+ - Ignore local `.env` files and clarify the required UTC timestamp format for campaign reads.
13
+ - Document checkout installation, read-only onboarding, complete task examples, API argument types, and response contracts.
14
+ - Add typed response contracts and consumer example checks.
15
+ - Accept the documented top-level success objects from `sendTestEmail` and `updateField`.
16
+ - Include known operation names, HTTP statuses, and documented API error codes in safe exception messages.
17
+ - Add project metadata, contribution guidance, and standard Conventional Commit types.
18
+
19
+ ## Compatibility policy
20
+
21
+ Before 1.0, a minor release may change public behavior with migration notes here. Patch releases preserve the documented public interface. After 1.0, releases follow semantic versioning.
22
+
23
+ The public interface consists of the documented `Unisender::Client`, transport injection contract, responses, errors, retry policy, helpers, and their RBS signatures. Internal modules are implementation details. JSON keys and values preserve upstream representations, including unknown fields; callers must allow for service-side additions. Changes to supported Ruby versions are announced here.
24
+
25
+ Local fake-response tests do not prove live-account compatibility. Upstream API changes can require a library update even when its Ruby interface is unchanged.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,43 @@
1
+ # Contributing
2
+
3
+ This repository is private. Collaborators can use the [issue tracker](https://github.com/svyatov/unisender-ruby/issues). Include the Ruby and gem versions, a minimal example with fake values, expected behavior, and a redacted exception message. Do not post API keys, contact data, export links, or raw request/response bodies.
4
+
5
+ ## Set up
6
+
7
+ Use Ruby 3.3 or newer and Bundler. CI checks Ruby 3.3, 3.4, and 4.0. Install [Betterleaks](https://github.com/betterleaks/betterleaks) using its upstream installation instructions and ensure `betterleaks` is on `PATH`. Then run:
8
+
9
+ ```sh
10
+ bin/setup
11
+ bundle exec rake check
12
+ ```
13
+
14
+ `bin/setup` installs development dependencies and Lefthook hooks. The complete check runs offline tests, RuboCop, RBS validation, Steep for the library and consumer examples, the dependency vulnerability audit, and the gem build. The audit updates its advisory database and needs network access.
15
+
16
+ ## Make a change
17
+
18
+ Use a branch such as `fix/error-context` or `feat/new-operation`. Read the official UniSender method documentation before changing a wire contract. Add a regression test with a deterministic transport, keep signatures in `sig/` synchronized, update the relevant docs, and add an entry under Unreleased in [CHANGELOG.md](CHANGELOG.md).
19
+
20
+ | Command | Purpose |
21
+ | --- | --- |
22
+ | `bundle exec rake test` | Minitest with 100% line and branch coverage across `lib/` and `scripts/` |
23
+ | `bundle exec rake lint` | Ruby style and packaging checks |
24
+ | `bundle exec rake format` | Safe RuboCop corrections |
25
+ | `bundle exec rake typecheck` | RBS validation, Steep, and 100% library typed-call coverage |
26
+ | `bundle exec rake audit` | Dependency vulnerability audit |
27
+ | `bundle exec rake build` | Build the gem in `pkg/` |
28
+ | `git diff --check` | Whitespace checks |
29
+
30
+ Coverage results are not merged with previous runs. Inspect `coverage/index.html` for missing lines or branches. A focused test run can fail the global coverage threshold; run the complete suite before submitting. Typed-call coverage measures the library; the consumer target separately checks that documented workflows type-check.
31
+
32
+ Pre-commit hooks scan staged changes for secrets, apply and stage safe Ruby formatting fixes, and check whitespace. Commit messages follow Conventional Commits, including `feat:`, `fix:`, `docs:`, `test:`, `refactor:`, `perf:`, `build:`, `ci:`, `chore:`, and `revert:`, with optional scope and `!`. Pre-push runs the complete suite. Put local overrides in ignored `lefthook-local.yml`.
33
+
34
+ ## Build and test the package
35
+
36
+ ```sh
37
+ bundle exec rake build
38
+ gem install --local ./pkg/unisender-ruby-0.1.0.gem
39
+ ```
40
+
41
+ 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.
42
+
43
+ Routine verification must not call a live account, send messages, or change contacts. Live checks require explicit authorization and a designated test account. Publishing and releases are separate maintainer actions.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leonid Svyatov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # Unisender
2
+
3
+ A Ruby client for the [UniSender marketing API](https://www.unisender.com/ru/support/category/api/). Ruby 3.3 or newer, no runtime gem dependencies. This client targets `api.unisender.com`, not UniSender Go.
4
+
5
+ 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.
6
+
7
+ ## Install
8
+
9
+ Add the gem to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem "unisender-ruby", "~> 0.1.0", require: "unisender"
13
+ ```
14
+
15
+ Then run `bundle install`. To install it directly, run `gem install unisender-ruby`. The package is named `unisender-ruby`; load it with `require "unisender"`.
16
+
17
+ ### Install from a checkout
18
+
19
+ In your application's Gemfile, point to this repository:
20
+
21
+ ```ruby
22
+ gem "unisender-ruby", path: "../unisender-ruby", require: "unisender"
23
+ ```
24
+
25
+ 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).
26
+
27
+ ## Make your first request
28
+
29
+ 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.
30
+
31
+ 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:
32
+
33
+ ```sh
34
+ set -a
35
+ . ./.env
36
+ set +a
37
+ bundle exec ruby examples/check_connection.rb
38
+ ```
39
+
40
+ 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.
41
+
42
+ This example only reads your lists. It does not create contacts or send messages:
43
+
44
+ <!-- example: examples/quickstart.rb -->
45
+ ```ruby
46
+ require "unisender"
47
+
48
+ client = Unisender::Client.new(api_key: ENV.fetch("UNISENDER_API_KEY"))
49
+ client.get_lists.result.each { |list| puts list.fetch("title") }
50
+ ```
51
+
52
+ Save it as `quickstart.rb` and run `bundle exec ruby quickstart.rb`. It prints one title per list, or nothing if the account has no lists. 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.
53
+
54
+ ## Subscribe a contact
55
+
56
+ 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.
57
+
58
+ <!-- example: examples/subscribe.rb -->
59
+ ```ruby
60
+ require "unisender"
61
+
62
+ client = Unisender::Client.new(api_key: ENV.fetch("UNISENDER_API_KEY"))
63
+ response = client.subscribe(
64
+ list_ids: [ENV.fetch("UNISENDER_LIST_ID")],
65
+ fields: { email: ENV.fetch("UNISENDER_CONTACT_EMAIL") }
66
+ )
67
+ puts response.result.fetch("person_id")
68
+ ```
69
+
70
+ 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/).
71
+
72
+ ## Complete workflows
73
+
74
+ Each example is a standalone Ruby file. Its header lists required environment variables and account effects.
75
+
76
+ | Task | Example | Effect |
77
+ | --- | --- | --- |
78
+ | Read lists | [quickstart.rb](examples/quickstart.rb) | Read only |
79
+ | Check credentials without printing account data | [check_connection.rb](examples/check_connection.rb) | Read only |
80
+ | Test without credentials | [offline.rb](examples/offline.rb) | No network |
81
+ | Subscribe a contact | [subscribe.rb](examples/subscribe.rb) | Changes contacts; sends confirmation |
82
+ | Create and send a campaign | [campaign.rb](examples/campaign.rb) | Sends to the selected list |
83
+ | Export contacts | [export.rb](examples/export.rb) | Starts and polls an export job |
84
+ | Import batches with partial-failure handling | [import.rb](examples/import.rb) | Changes contacts |
85
+ | Track events with partial-failure handling | [events.rb](examples/events.rb) | Records events; can trigger automation |
86
+
87
+ ## Responses and failures
88
+
89
+ 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.
90
+
91
+ 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.
92
+
93
+ ```ruby
94
+ begin
95
+ client.get_lists
96
+ rescue Unisender::APIError => error
97
+ warn error.message
98
+ # Fix credentials, permissions, or parameters using error.code.
99
+ rescue Unisender::HTTPError => error
100
+ warn error.message
101
+ # Check error.response.status and error.response.headers.
102
+ rescue Unisender::TransportError, Unisender::InvalidResponseError => error
103
+ warn error.message
104
+ # Check service availability before deciding whether to repeat the operation.
105
+ end
106
+ ```
107
+
108
+ `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`.
109
+
110
+ 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.
111
+
112
+ 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.
113
+
114
+ ## Timeouts, retries, and pagination
115
+
116
+ ```ruby
117
+ client = Unisender::Client.new(
118
+ api_key: ENV.fetch("UNISENDER_API_KEY"),
119
+ transport: Unisender::Transport.new(open_timeout: 5, read_timeout: 60, write_timeout: 30),
120
+ retry_policy: Unisender::RetryPolicy.new(max_retries: 2, max_elapsed: 60)
121
+ )
122
+ ```
123
+
124
+ 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.
125
+
126
+ `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/).
127
+
128
+ ## Low-level access and types
129
+
130
+ `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.
131
+
132
+ 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.
133
+
134
+ ## Project information
135
+
136
+ - [Contributing, local checks, and package verification](CONTRIBUTING.md)
137
+ - [Changes and compatibility policy](CHANGELOG.md)
138
+ - [Source repository](https://github.com/svyatov/unisender-ruby) and [issue reports](https://github.com/svyatov/unisender-ruby/issues)
139
+
140
+ The repository is currently private; its GitHub links require access. Tests use fake credentials and deterministic responses. They do not establish compatibility with a live account. Released under the [MIT License](LICENSE.txt).