broadcast-ruby 0.5.0 → 0.7.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: 06eed741f3800e40f0734e35389d38fe527b22508b4bd623621a7eb54bb0ed3e
4
- data.tar.gz: 8dd349d9829003e8895f1d2612d5c86cd5248037307a32313a4e6b05f943a1d1
3
+ metadata.gz: 6691d4bb7e0fb74c2f3fe326d1ef2ae6797ec957f730be3d62287c07b13b2b65
4
+ data.tar.gz: 6117bc0c528ddb576ec51eafe1c56071b05fc354d06cbe66b067effbe2f8e560
5
5
  SHA512:
6
- metadata.gz: 425b9de89f5fc7448483c66e082a42ef1a0b81dc13e7e8ec0067d5c43cf02da3197f7c80826b1b896ea59fe79abe631fd37b2b38e7180fa4f37320e58698d91d
7
- data.tar.gz: f3d825c247f222b67e8d76e63a9fbaf14cab5a6a1ad81768ec22a9712c0a56de8ca10733b99a5e26755ae20618fe25228465935129e33a7d4254312ee6b8c01d
6
+ metadata.gz: 86f1f4579ff54f8677f1848efa21d097bea66ab5e2102fcc05763021a4f7ff05b34818b93e506631f2cd241bbe87c21d587bc0f6ff60f72adadad4f3acefc009
7
+ data.tar.gz: b0a6d3889b396bce312a174f3da12e87a3f20ac729c91c26891a03f1b6f7f1f696f02d1c5681773b1f3fdc649034426d52a637891fe3153b8e0d0e4b044fc6a1
data/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ## [0.7.0] - 2026-10-06
8
+
9
+ ### Added
10
+
11
+ - `Topics` resource (`client.topics`): `list`, `get_topic`, `create`, `update`,
12
+ `delete` for `/api/v1/topics` (subscriber topics; uses the subscriber
13
+ permissions). Broadcasts and sequences accept `topic_id`.
14
+ - `Subscribers#update` takes `custom_data_mode:` (`'replace'` default, or
15
+ `'merge'`: change only the keys sent, `nil` deletes a key).
16
+ - `subscriber.preferences_updated` webhook event type, in
17
+ `Broadcast::Webhook::SUBSCRIBER_EVENTS` and `EVENT_TYPES` (now 35).
18
+
19
+ ## [0.6.0] - 2026-10-03
20
+
21
+ ### Added
22
+
23
+ - `Migration#unsubscribed_emails` for `GET /api/migration/v1/unsubscribed_emails`,
24
+ the channel's own suppression list (rows `id`, `email`,
25
+ `broadcast_channel_id`, `created_at`, `updated_at`). `each_record(:unsubscribed_emails)`
26
+ pages it. `suppressions` returns only the global suppression list, so an
27
+ export needs both.
28
+
5
29
  ## [0.5.0] - 2026-09-25
6
30
 
7
31
  ### Added
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- broadcast-ruby (0.5.0)
4
+ broadcast-ruby (0.7.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.5.0)
180
+ broadcast-ruby (0.7.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
@@ -712,6 +712,38 @@ client.broadcasts.statistics_links(1, sort: 'clicks', order: 'desc')
712
712
 
713
713
  ---
714
714
 
715
+ ## Topics
716
+
717
+ A topic is one kind of email subscribers opt in to or out of (webinars, offers).
718
+ Its value lives in a top-level `custom_data` key (`true`, `false`, or no value)
719
+ or in a tag. `unset_receives` decides, at send time, whether a subscriber with no
720
+ value receives it; Broadcast never writes a default. A broadcast or sequence with
721
+ a `topic_id` reaches only the topic's receivers: (segments) AND topic.
722
+
723
+ **Required permissions:** `subscribers_read`, `subscribers_write`
724
+
725
+ ```ruby
726
+ client.topics.list['data'] # => [{'id' => 1, 'name' => 'Webinars', 'custom_data_key' => 'sub_webinars', ...}]
727
+ client.topics.get_topic(1)
728
+
729
+ topic = client.topics.create(name: 'Webinars', custom_data_key: 'sub_webinars')
730
+ client.topics.create(name: 'News', storage: 'tag', tag_name: 'news')
731
+ client.topics.update(topic['id'], unset_receives: false)
732
+ client.topics.delete(topic['id']) # 422 while a broadcast or sequence uses it
733
+
734
+ # Send to a topic
735
+ client.broadcasts.create(subject: 'Webinar', body: '...', segment_ids: [2], topic_id: topic['id'])
736
+
737
+ # Change only the keys you send (nil deletes a key); the default replaces custom_data
738
+ client.subscribers.update('jane@example.com', custom_data: { sub_webinars: false }, custom_data_mode: 'merge')
739
+ ```
740
+
741
+ The subscriber JSON has `topics`: each topic's stored value (`true`, `false`, or
742
+ `nil`). Topic changes fire `subscriber.preferences_updated` (from/to per topic,
743
+ `changed_at`, `change_source`) as well as `subscriber.updated`.
744
+
745
+ ---
746
+
715
747
  ## Segments
716
748
 
717
749
  Define subscriber groups using rules for targeted broadcasts and sequence enrollment.
@@ -1131,7 +1163,7 @@ secret = result['secret']
1131
1163
 
1132
1164
  # Every valid event type is available as a constant. An unknown event type is
1133
1165
  # dropped server-side rather than rejected, so subscribe from these.
1134
- Broadcast::Webhook::EVENT_TYPES # all 34
1166
+ Broadcast::Webhook::EVENT_TYPES # all 35
1135
1167
  Broadcast::Webhook::EMAIL_EVENTS # email.sent, email.delivered, ...
1136
1168
  Broadcast::Webhook::SUBSCRIBER_EVENTS # subscriber.created, ..., subscribers.purged
1137
1169
  Broadcast::Webhook::BROADCAST_EVENTS # broadcast.sending, broadcast.sent, ...
@@ -1380,10 +1412,14 @@ Available collections:
1380
1412
  ```
1381
1413
  channels subscribers templates segments sequences email_servers
1382
1414
  opt_in_forms broadcasts outbound_receipts webhook_endpoints tokens
1383
- suppressions tags users link_redirects link_clicks
1384
- subscriber_histories file_assets
1415
+ suppressions unsubscribed_emails tags users link_redirects
1416
+ link_clicks subscriber_histories file_assets
1385
1417
  ```
1386
1418
 
1419
+ `suppressions` is the global suppression list (GlobalSuppression rows, global
1420
+ or scoped to the channel). `unsubscribed_emails` is the channel's own
1421
+ suppression list. Export both to keep every suppressed address.
1422
+
1387
1423
  File assets are downloaded separately and return raw bytes:
1388
1424
 
1389
1425
  ```ruby
data/SDK-COVERAGE.md CHANGED
@@ -53,7 +53,7 @@ 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 18 migration collections are
56
+ - **Metaprogrammed endpoints are declared.** The 19 migration collections are
57
57
  defined by `define_method` over `Migration::COLLECTIONS`, so no path literal
58
58
  exists to scan. They are listed in `.api-coverage.yml` and marked
59
59
  *(generated)* below. That file is produced from the gem's own constants — it
@@ -268,6 +268,7 @@ on the client and the gem attaches it.
268
268
  | GET | `/api/migration/v1/webhook_endpoints` | *(generated)* `Migration#webhook_endpoints` |
269
269
  | GET | `/api/migration/v1/tokens` | *(generated)* `Migration#tokens` |
270
270
  | GET | `/api/migration/v1/suppressions` | *(generated)* `Migration#suppressions` |
271
+ | GET | `/api/migration/v1/unsubscribed_emails` | *(generated)* `Migration#unsubscribed_emails` |
271
272
  | GET | `/api/migration/v1/tags` | *(generated)* `Migration#tags` |
272
273
  | GET | `/api/migration/v1/users` | *(generated)* `Migration#users` |
273
274
  | GET | `/api/migration/v1/link_redirects` | *(generated)* `Migration#link_redirects` |
@@ -330,7 +331,7 @@ Inbound webhooks — the app calling you.
330
331
  | HMAC-SHA256 verification (`v1,<base64>`) | ✅ `Webhook.verify` |
331
332
  | 5-minute timestamp tolerance | ✅ Replay protection |
332
333
  | Constant-time comparison | ✅ `OpenSSL.fixed_length_secure_compare` |
333
- | Event type constants | ✅ `Webhook::EVENT_TYPES` — 34 values |
334
+ | Event type constants | ✅ `Webhook::EVENT_TYPES` — 35 values |
334
335
 
335
336
  Categories: `EMAIL_EVENTS` (8), `SUBSCRIBER_EVENTS` (7), `BROADCAST_EVENTS` (8),
336
337
  `SEQUENCE_EVENTS` (7), `SYSTEM_EVENTS` (2).
@@ -99,6 +99,10 @@ module Broadcast
99
99
  @segments ||= Resources::Segments.new(self)
100
100
  end
101
101
 
102
+ def topics
103
+ @topics ||= Resources::Topics.new(self)
104
+ end
105
+
102
106
  def templates
103
107
  @templates ||= Resources::Templates.new(self)
104
108
  end
@@ -20,8 +20,8 @@ module Broadcast
20
20
  COLLECTIONS = %i[
21
21
  channels subscribers templates segments sequences email_servers
22
22
  opt_in_forms broadcasts outbound_receipts webhook_endpoints tokens
23
- suppressions tags users link_redirects link_clicks subscriber_histories
24
- file_assets
23
+ suppressions unsubscribed_emails tags users link_redirects link_clicks
24
+ subscriber_histories file_assets
25
25
  ].freeze
26
26
 
27
27
  COLLECTIONS.each do |collection|
@@ -57,8 +57,12 @@ module Broadcast
57
57
  post('/api/v1/subscribers.json', payload)
58
58
  end
59
59
 
60
- def update(email, **attrs)
61
- patch('/api/v1/subscribers.json', { email: email, subscriber: attrs })
60
+ # custom_data_mode: 'replace' (the API default) stores custom_data as sent;
61
+ # 'merge' changes only the keys sent, and a nil value deletes that key.
62
+ def update(email, custom_data_mode: nil, **attrs)
63
+ body = { email: email, subscriber: attrs }
64
+ body[:custom_data_mode] = custom_data_mode if custom_data_mode
65
+ patch('/api/v1/subscribers.json', body)
62
66
  end
63
67
 
64
68
  def add_tags(email, tags)
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Broadcast
4
+ module Resources
5
+ # Subscriber topics: kinds of email subscribers opt in to or out of. A topic
6
+ # reads a top-level custom_data key (true, false, or no value) or a tag.
7
+ # Uses the token's subscriber permissions.
8
+ class Topics < Base
9
+ def list(**params)
10
+ get('/api/v1/topics.json', params)
11
+ end
12
+
13
+ def get_topic(id)
14
+ get("/api/v1/topics/#{id}.json")
15
+ end
16
+
17
+ def create(**attrs)
18
+ post('/api/v1/topics', { topic: attrs })
19
+ end
20
+
21
+ def update(id, **attrs)
22
+ patch("/api/v1/topics/#{id}", { topic: attrs })
23
+ end
24
+
25
+ # Refused (422) while a broadcast or sequence uses the topic.
26
+ def delete(id)
27
+ @client.request(:delete, "/api/v1/topics/#{id}")
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Broadcast
4
- VERSION = '0.5.0'
4
+ VERSION = '0.7.0'
5
5
  end
@@ -18,7 +18,7 @@ module Broadcast
18
18
  # subscribers.* are sent once for a whole-list purge, in place of a
19
19
  # subscriber.deleted per row.
20
20
  SUBSCRIBER_EVENTS = %w[
21
- subscriber.created subscriber.updated subscriber.deleted
21
+ subscriber.created subscriber.updated subscriber.preferences_updated subscriber.deleted
22
22
  subscriber.subscribed subscriber.unsubscribed subscriber.bounced
23
23
  subscriber.complained subscribers.purged subscribers.purge_failed
24
24
  ].freeze
data/lib/broadcast.rb CHANGED
@@ -13,6 +13,7 @@ require_relative 'broadcast/resources/subscribers'
13
13
  require_relative 'broadcast/resources/sequences'
14
14
  require_relative 'broadcast/resources/broadcasts'
15
15
  require_relative 'broadcast/resources/segments'
16
+ require_relative 'broadcast/resources/topics'
16
17
  require_relative 'broadcast/resources/templates'
17
18
  require_relative 'broadcast/resources/webhook_endpoints'
18
19
  require_relative 'broadcast/resources/transactionals'
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.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Chiu
@@ -61,6 +61,7 @@ files:
61
61
  - lib/broadcast/resources/subscribers.rb
62
62
  - lib/broadcast/resources/suppressions.rb
63
63
  - lib/broadcast/resources/templates.rb
64
+ - lib/broadcast/resources/topics.rb
64
65
  - lib/broadcast/resources/transactionals.rb
65
66
  - lib/broadcast/resources/users.rb
66
67
  - lib/broadcast/resources/webhook_endpoints.rb