broadcast-ruby 0.9.0 → 0.11.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: 46cb94477a871cda3ac8f5a69d2d947b2ec87b5dd4454a1ec8d179cec7f25b9a
4
- data.tar.gz: 38f72f3aecd7376ed6f072e831049614ae5f329c8e92cb0e1aeb3d26648f8829
3
+ metadata.gz: 046bdebddc0d68f63776039530a40783734df10ca736d3f8212f598f72927c12
4
+ data.tar.gz: 5f1e53c5af9d8654410849eaf485e3f45657e02fb30cb575254585e49eda88e6
5
5
  SHA512:
6
- metadata.gz: e46bf11d5142f63a5bde2fc5d6d0c34c5d352fc05e428e1c3bc05931e346c9cff163f664d611f2ea88c1d5af819ed9d09b2fde518d5fe4fd6629ab650626bb4f
7
- data.tar.gz: ca25863674f4560506aca95d62739ca8998a0671cfc52561b99b1dac26f6c519d6ff19c1781b121a4f3b72d6902b5e00360f30418d954aac3a34ad3fbbe83515
6
+ metadata.gz: 718ae1afd1950d245ae33084763e96fcc9c186ca56ebe6e4f44a8109b944bf8674d87ca9357ac9782c6ebac6ee4f8e46a6a92a86053c1dc73099c13fa5a1fb8d
7
+ data.tar.gz: d61a1d23973470942c06de04d732b01d0df32e3ab098f88ac7b82d68b68a142535de1ab93085f53cf0b18a4981f1c52b0d77c91c3217db2a566d774d9e0be10b
data/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.11.0] - 2026-11-07
8
+
9
+ ### Removed
10
+
11
+ - **Breaking:** `client.migration` (`Broadcast::Resources::Migration`): `manifest`,
12
+ the 19 export collections, `each_record` and `download_file_asset`. Broadcast 3
13
+ removes the `/api/migration/v1` export API. A server before 3.0 still serves
14
+ it; stay on 0.10.x to keep using it.
15
+
16
+ ## [0.10.0] - 2026-10-17
17
+
18
+ ### Added
19
+
20
+ - `Broadcast::Resources::OptInForms::TRIGGER_FREQUENCIES` includes `once_per_days` (Broadcast 2.49): the popup shows
21
+ again after `trigger_settings.frequency_cookie_days` days, a whole number, 1 or
22
+ more; blank means 30. The client still sends any word as it is, so nothing
23
+ changes for a server before 2.49, which refuses the word with 422.
24
+
7
25
  ## [0.9.0] - 2026-10-16
8
26
 
9
27
  ### Added
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- broadcast-ruby (0.9.0)
4
+ broadcast-ruby (0.11.0)
5
5
  base64
6
6
 
7
7
  GEM
@@ -177,7 +177,7 @@ CHECKSUMS
177
177
  ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
178
178
  base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
179
179
  bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
180
- broadcast-ruby (0.9.0)
180
+ broadcast-ruby (0.11.0)
181
181
  builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
182
182
  concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
183
183
  connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
data/README.md CHANGED
@@ -986,6 +986,10 @@ client.opt_in_forms.update(1, enabled: false)
986
986
  # Broadcast::ValidationError (422).
987
987
  client.opt_in_forms.update(1, trigger_settings: { frequency: 'once_per_day' })
988
988
 
989
+ # Every N days (Broadcast 2.49 and later): frequency_cookie_days is a whole
990
+ # number, 1 or more; blank means 30.
991
+ client.opt_in_forms.update(1, trigger_settings: { frequency: 'once_per_days', frequency_cookie_days: 7 })
992
+
989
993
  # Delete
990
994
  client.opt_in_forms.delete(1)
991
995
  ```
@@ -1457,75 +1461,6 @@ raise 'channel not ready to send' unless client.status.dig('readiness', 'broadca
1457
1461
 
1458
1462
  ---
1459
1463
 
1460
- ## Export & Migration
1461
-
1462
- Read-only endpoints under `/api/migration/v1` for backups and moving a channel
1463
- between instances. Two constraints differ from the rest of the API:
1464
-
1465
- - **Admin tokens only.** Channel-scoped tokens are rejected.
1466
- - **`broadcast_channel_id` is required on every call.** Set it once on the
1467
- client and the gem attaches it for you.
1468
-
1469
- ```ruby
1470
- client = Broadcast::Client.new(
1471
- api_token: ENV['BROADCAST_ADMIN_TOKEN'],
1472
- host: 'https://mail.example.com',
1473
- broadcast_channel_id: 1
1474
- )
1475
-
1476
- # Size the export first
1477
- manifest = client.migration.manifest
1478
- manifest['counts'] # => { 'subscribers' => 5000, 'templates' => 12, ... }
1479
- manifest['export_format_version']
1480
-
1481
- # Time-bounded counts (broadcasts, receipts, histories) default to 90 days
1482
- client.migration.manifest(days_history: 365)
1483
- ```
1484
-
1485
- Each collection is a paginated list returning `data` and `pagination`:
1486
-
1487
- ```ruby
1488
- page = client.migration.subscribers(limit: 250, offset: 0)
1489
- page['data']
1490
- page['pagination'] # => { 'total' => 5000, 'limit' => 250, 'offset' => 0, 'has_more' => true }
1491
- ```
1492
-
1493
- `each_record` handles the paging for you, advancing by the page size the server
1494
- actually granted rather than the one you asked for:
1495
-
1496
- ```ruby
1497
- CSV.open('subscribers.csv', 'w') do |csv|
1498
- client.migration.each_record(:subscribers) do |subscriber|
1499
- csv << [subscriber['email'], subscriber['first_name'], subscriber['created_at']]
1500
- end
1501
- end
1502
-
1503
- # Without a block you get an Enumerator
1504
- client.migration.each_record(:tags).map { |tag| tag['name'] }
1505
- ```
1506
-
1507
- Available collections:
1508
-
1509
- ```
1510
- channels subscribers templates segments sequences email_servers
1511
- opt_in_forms broadcasts outbound_receipts webhook_endpoints tokens
1512
- suppressions unsubscribed_emails tags users link_redirects
1513
- link_clicks subscriber_histories file_assets
1514
- ```
1515
-
1516
- `suppressions` is the global suppression list (GlobalSuppression rows, global
1517
- or scoped to the channel). `unsubscribed_emails` is the channel's own
1518
- suppression list. Export both to keep every suppressed address.
1519
-
1520
- File assets are downloaded separately and return raw bytes:
1521
-
1522
- ```ruby
1523
- bytes = client.migration.download_file_asset(7)
1524
- File.binwrite('logo.png', bytes)
1525
- ```
1526
-
1527
- ---
1528
-
1529
1464
  ## Errors
1530
1465
 
1531
1466
  All API errors inherit from `Broadcast::Error`. Put specific errors before general ones:
data/SDK-COVERAGE.md CHANGED
@@ -53,11 +53,6 @@ Two details worth knowing when reading the map below:
53
53
  - **PATCH is canonical.** Rails' `resources` routes `update` to both PATCH and
54
54
  PUT. They are one operation; the generator drops the PUT alias. Counting both
55
55
  would inflate the denominator by 8 and make this gem look 8 endpoints short.
56
- - **Metaprogrammed endpoints are declared.** The 19 migration collections are
57
- defined by `define_method` over `Migration::COLLECTIONS`, so no path literal
58
- exists to scan. They are listed in `.api-coverage.yml` and marked
59
- *(generated)* below. That file is produced from the gem's own constants — it
60
- is not a hand-written promise.
61
56
 
62
57
  ---
63
58
 
@@ -247,39 +242,6 @@ payload so a fetch-modify-save cannot destroy the stored credential.
247
242
  naming the missing prerequisites. `trigger_run` returns 202 — generation is
248
243
  asynchronous, so poll `runs`.
249
244
 
250
- ### Migration / export — 20/20
251
-
252
- Admin tokens only. `broadcast_channel_id` is required on every call; set it once
253
- on the client and the gem attaches it.
254
-
255
- | Method | Path | Gem |
256
- |---|---|---|
257
- | GET | `/api/migration/v1/manifest` | `Migration#manifest` |
258
- | GET | `/api/migration/v1/file_assets/{id}/download` | `Migration#download_file_asset` — raw bytes |
259
- | GET | `/api/migration/v1/channels` | *(generated)* `Migration#channels` |
260
- | GET | `/api/migration/v1/subscribers` | *(generated)* `Migration#subscribers` |
261
- | GET | `/api/migration/v1/templates` | *(generated)* `Migration#templates` |
262
- | GET | `/api/migration/v1/segments` | *(generated)* `Migration#segments` |
263
- | GET | `/api/migration/v1/sequences` | *(generated)* `Migration#sequences` |
264
- | GET | `/api/migration/v1/email_servers` | *(generated)* `Migration#email_servers` |
265
- | GET | `/api/migration/v1/opt_in_forms` | *(generated)* `Migration#opt_in_forms` |
266
- | GET | `/api/migration/v1/broadcasts` | *(generated)* `Migration#broadcasts` |
267
- | GET | `/api/migration/v1/outbound_receipts` | *(generated)* `Migration#outbound_receipts` |
268
- | GET | `/api/migration/v1/webhook_endpoints` | *(generated)* `Migration#webhook_endpoints` |
269
- | GET | `/api/migration/v1/tokens` | *(generated)* `Migration#tokens` |
270
- | GET | `/api/migration/v1/suppressions` | *(generated)* `Migration#suppressions` |
271
- | GET | `/api/migration/v1/unsubscribed_emails` | *(generated)* `Migration#unsubscribed_emails` |
272
- | GET | `/api/migration/v1/tags` | *(generated)* `Migration#tags` |
273
- | GET | `/api/migration/v1/users` | *(generated)* `Migration#users` |
274
- | GET | `/api/migration/v1/link_redirects` | *(generated)* `Migration#link_redirects` |
275
- | GET | `/api/migration/v1/link_clicks` | *(generated)* `Migration#link_clicks` |
276
- | GET | `/api/migration/v1/subscriber_histories` | *(generated)* `Migration#subscriber_histories` |
277
- | GET | `/api/migration/v1/file_assets` | *(generated)* `Migration#file_assets` |
278
-
279
- `Migration#each_record(:collection)` pages any of them automatically, advancing
280
- by the page size the **server granted** rather than the one requested (the
281
- server clamps `limit` to 250).
282
-
283
245
  ---
284
246
 
285
247
  ## Transport coverage
@@ -301,10 +263,10 @@ complete while quietly losing information.
301
263
  | Retry on 5xx and timeout | ✅ | `retry_attempts`, `retry_delay` |
302
264
  | Typed errors | ✅ | 401/403/404/409/422/429/5xx each distinct |
303
265
  | Redirect handling | ✅ | Same-host GET follows; writes and cross-host refuse |
304
- | Raw/text responses | ✅ | `client.skill`, `download_file_asset` |
266
+ | Raw/text responses | ✅ | `client.skill` |
305
267
  | Channel scoping | ✅ | `broadcast_channel_id:`, `with_channel(id) { }` |
306
268
  | Debug logging | ✅ | Credentials redacted |
307
- | Pagination helper | ⚠️ Migration only | `each_record`; v1 endpoints page manually |
269
+ | Pagination helper | ❌ | v1 endpoints page manually |
308
270
  | ActionMailer integration | ✅ | `:broadcast` delivery method + Railtie |
309
271
 
310
272
  Error mapping in full:
@@ -355,9 +317,9 @@ this is an API gap, not a gem gap, and nothing here is actionable in this repo.
355
317
  | **Channel management** | Web UI | Creating channels, domain/sender setup, onboarding |
356
318
  | **User & permission management** | Web UI | Users, system permissions, per-channel permissions |
357
319
  | **Settings** | Web UI | General, AI, DNS checks, ESP integrations, sender details |
358
- | **Global suppressions** | Web UI + migration export | Read-only via `migration.suppressions`; no v1 CRUD |
359
- | **File assets** | Web UI + migration export | Read-only via migration; no v1 upload |
360
- | **Link tracking config** | Web UI | Click data readable via `statistics_links` and migration |
320
+ | **Global suppressions** | Web UI | No v1 CRUD |
321
+ | **File assets** | Web UI | No v1 upload |
322
+ | **Link tracking config** | Web UI | Click data readable via `statistics_links` |
361
323
  | **Admin / platform** | Web UI | Admin API tokens, SaaS platform admin |
362
324
  | **Confirmation templates & pages** | Partly API | Configurable as template attributes; no dedicated endpoints |
363
325
 
@@ -158,12 +158,6 @@ module Broadcast
158
158
  @file_assets ||= Resources::FileAssets.new(self)
159
159
  end
160
160
 
161
- # Read-only export endpoints under /api/migration/v1. Requires an admin
162
- # (system) API token.
163
- def migration
164
- @migration ||= Resources::Migration.new(self)
165
- end
166
-
167
161
  # @api private
168
162
  # rubocop:disable Metrics/ParameterLists -- transport options passed straight through to Connection
169
163
  def request(method, path, body_or_params = nil, headers: {}, raw: false, multipart: false)
@@ -9,7 +9,10 @@ module Broadcast
9
9
  # sees a popup. always and every_visit mean the same. The server refuses any
10
10
  # other word with 422 (Broadcast::ValidationError); the client does not
11
11
  # check, so an older server is never refused a word it would take.
12
- TRIGGER_FREQUENCIES = %w[always every_visit once_per_session once_per_day once_per_week once].freeze
12
+ # once_per_days (Broadcast 2.49 and later) shows the popup again after
13
+ # trigger_settings[:frequency_cookie_days] days: a whole number, 1 or
14
+ # more; blank means 30.
15
+ TRIGGER_FREQUENCIES = %w[always every_visit once_per_session once_per_day once_per_week once once_per_days].freeze
13
16
 
14
17
  # List opt-in forms.
15
18
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Broadcast
4
- VERSION = '0.9.0'
4
+ VERSION = '0.11.0'
5
5
  end
data/lib/broadcast.rb CHANGED
@@ -27,7 +27,6 @@ require_relative 'broadcast/resources/suppressions'
27
27
  require_relative 'broadcast/resources/global_suppressions'
28
28
  require_relative 'broadcast/resources/channel'
29
29
  require_relative 'broadcast/resources/file_assets'
30
- require_relative 'broadcast/resources/migration'
31
30
 
32
31
  # ActionMailer integration — only loaded when Rails is present
33
32
  if defined?(Rails::Railtie)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: broadcast-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Chiu
@@ -56,7 +56,6 @@ files:
56
56
  - lib/broadcast/resources/email_servers.rb
57
57
  - lib/broadcast/resources/file_assets.rb
58
58
  - lib/broadcast/resources/global_suppressions.rb
59
- - lib/broadcast/resources/migration.rb
60
59
  - lib/broadcast/resources/opt_in_forms.rb
61
60
  - lib/broadcast/resources/segments.rb
62
61
  - lib/broadcast/resources/sequences.rb
@@ -93,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
92
  - !ruby/object:Gem::Version
94
93
  version: '0'
95
94
  requirements: []
96
- rubygems_version: 4.0.16
95
+ rubygems_version: 4.0.17
97
96
  specification_version: 4
98
97
  summary: Ruby client for the Broadcast email platform
99
98
  test_files: []
@@ -1,75 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Broadcast
4
- module Resources
5
- # Read-only export endpoints under /api/migration/v1 — the surface behind
6
- # backups and instance-to-instance migration.
7
- #
8
- # Two things differ from the v1 API:
9
- #
10
- # 1. **Admin tokens only.** Channel-scoped tokens are rejected outright;
11
- # the controller looks up AdminApiToken and nothing else.
12
- # 2. **broadcast_channel_id is required on every call.** Set it once via
13
- # `Client.new(broadcast_channel_id:)` or `client.with_channel(id)` and
14
- # the gem attaches it automatically; otherwise pass it per call.
15
- #
16
- # Every list endpoint pages with `limit:` (1..250, default 250) and
17
- # `offset:`, and returns `{"data" => [...], "pagination" => {...}}`.
18
- class Migration < Base
19
- # Endpoints that are a plain paginated list of the channel's records.
20
- COLLECTIONS = %i[
21
- channels subscribers templates segments sequences email_servers
22
- opt_in_forms broadcasts outbound_receipts webhook_endpoints tokens
23
- suppressions unsubscribed_emails tags users link_redirects link_clicks
24
- subscriber_histories file_assets
25
- ].freeze
26
-
27
- COLLECTIONS.each do |collection|
28
- define_method(collection) do |**params|
29
- get("/api/migration/v1/#{collection}", params)
30
- end
31
- end
32
-
33
- # Export summary: format version, channel identity, per-resource counts,
34
- # and recent-history totals. Call this first to size an export.
35
- #
36
- # @param days_history [Integer] window for the time-bounded counts
37
- # (broadcasts, receipts, histories). Server clamps to 1..365, default 90.
38
- def manifest(**params)
39
- get('/api/migration/v1/manifest', params)
40
- end
41
-
42
- # Binary contents of a stored file asset. Returns a String of bytes, not
43
- # JSON — write it straight to disk.
44
- def download_file_asset(id, **params)
45
- @client.request(:get, "/api/migration/v1/file_assets/#{id}/download", params, raw: true)
46
- end
47
-
48
- # Pages through a collection, yielding each record.
49
- #
50
- # client.migration.each_record(:subscribers) { |sub| csv << sub }
51
- #
52
- # Stops when the server reports `has_more: false`, so it stays correct if
53
- # the page size is clamped server-side.
54
- def each_record(collection, limit: 250, **params, &block)
55
- return enum_for(:each_record, collection, limit: limit, **params) unless block_given?
56
-
57
- offset = 0
58
- loop do
59
- page = public_send(collection, limit: limit, offset: offset, **params)
60
- records = Array(page['data'])
61
- records.each(&block)
62
-
63
- pagination = page['pagination'] || {}
64
- break unless pagination['has_more']
65
-
66
- # Advance by what the server actually returned — it clamps `limit`.
67
- advanced = pagination['limit'] || records.size
68
- break if advanced.to_i.zero?
69
-
70
- offset += advanced.to_i
71
- end
72
- end
73
- end
74
- end
75
- end