broadcast-ruby 0.6.0 → 0.8.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 +4 -4
- data/CHANGELOG.md +39 -0
- data/Gemfile.lock +2 -2
- data/README.md +46 -3
- data/SDK-COVERAGE.md +1 -1
- data/lib/broadcast/client.rb +4 -0
- data/lib/broadcast/resources/autopilots.rb +1 -0
- data/lib/broadcast/resources/opt_in_forms.rb +10 -0
- data/lib/broadcast/resources/subscribers.rb +13 -3
- data/lib/broadcast/resources/topics.rb +31 -0
- data/lib/broadcast/version.rb +1 -1
- data/lib/broadcast/webhook.rb +7 -4
- data/lib/broadcast.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 722b6b8aba5a3d87fbdacbab42f757071d0f4b7bc548a803f9b28fb0b4596ec9
|
|
4
|
+
data.tar.gz: 78c247ac8e18e66f3fff0e380b8a0d299047d15994765a0ad6ddb8f13cb766ba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a3cd09cc4c96d57ed30c64c8c48fade4f75bb6f5c15136819f7725e9eea1f8811b8e3bd91ec70125d92ad8411fcf60ca04cc0b5461bd50858e5f6c611f2246fe
|
|
7
|
+
data.tar.gz: edf1130a392a68a08fc95c1a29619e1a0399dbe0373c85048b6c45bba12a8dff88fb9ed84b1364c06ba5b5c9fa2c90e5da8fd083bd99b7d832a122cb72d3501e
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,45 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.8.0] - 2026-10-10
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `email.send_delayed` and `broadcast.batch_completed` in
|
|
12
|
+
`Broadcast::Webhook::EVENT_TYPES` (and in `EMAIL_EVENTS` /
|
|
13
|
+
`BROADCAST_EVENTS`), matching the server (Broadcast 2.43.0).
|
|
14
|
+
`email.send_delayed` fires when a transactional email waits for room under
|
|
15
|
+
its server's hourly limit.
|
|
16
|
+
- `excluded_segment_ids` on broadcasts, sequences and autopilots (create,
|
|
17
|
+
update, and in the returned record): segments a send never reaches. The
|
|
18
|
+
server refuses a segment that is both sent to and excluded with 422.
|
|
19
|
+
- `subscribers.create(unsubscribed_at: ...)`: creates a subscriber who has
|
|
20
|
+
already unsubscribed, in one request, with any token. Before, the server
|
|
21
|
+
ignored the field and a migration had to create, then unsubscribe.
|
|
22
|
+
|
|
23
|
+
- `Broadcast::Resources::OptInForms::TRIGGER_FREQUENCIES`: the words
|
|
24
|
+
`trigger_settings[:frequency]` accepts (`always`, `every_visit`,
|
|
25
|
+
`once_per_session`, `once_per_day`, `once_per_week`, `once`).
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Opt-in forms: the server now refuses an unknown `trigger_settings`
|
|
30
|
+
frequency word with 422 (`Broadcast::ValidationError`). Before, it saved
|
|
31
|
+
any word, and an unknown one showed the popup on every visit. The client
|
|
32
|
+
still sends what it is given.
|
|
33
|
+
|
|
34
|
+
## [0.7.0] - 2026-10-06
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- `Topics` resource (`client.topics`): `list`, `get_topic`, `create`, `update`,
|
|
39
|
+
`delete` for `/api/v1/topics` (subscriber topics; uses the subscriber
|
|
40
|
+
permissions). Broadcasts and sequences accept `topic_id`.
|
|
41
|
+
- `Subscribers#update` takes `custom_data_mode:` (`'replace'` default, or
|
|
42
|
+
`'merge'`: change only the keys sent, `nil` deletes a key).
|
|
43
|
+
- `subscriber.preferences_updated` webhook event type, in
|
|
44
|
+
`Broadcast::Webhook::SUBSCRIBER_EVENTS` and `EVENT_TYPES` (now 35).
|
|
45
|
+
|
|
7
46
|
## [0.6.0] - 2026-10-03
|
|
8
47
|
|
|
9
48
|
### Added
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
broadcast-ruby (0.
|
|
4
|
+
broadcast-ruby (0.8.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.
|
|
180
|
+
broadcast-ruby (0.8.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,44 @@ 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
|
+
# Send to everyone except a segment (also on sequences and autopilots)
|
|
738
|
+
client.broadcasts.create(subject: 'Offer', body: '...', excluded_segment_ids: [competitors['id']])
|
|
739
|
+
|
|
740
|
+
# Migrate someone who already unsubscribed, in one request
|
|
741
|
+
client.subscribers.create(email: 'opted-out@example.com', unsubscribed_at: '2025-03-14T09:30:00Z')
|
|
742
|
+
|
|
743
|
+
# Change only the keys you send (nil deletes a key); the default replaces custom_data
|
|
744
|
+
client.subscribers.update('jane@example.com', custom_data: { sub_webinars: false }, custom_data_mode: 'merge')
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
The subscriber JSON has `topics`: each topic's stored value (`true`, `false`, or
|
|
748
|
+
`nil`). Topic changes fire `subscriber.preferences_updated` (from/to per topic,
|
|
749
|
+
`changed_at`, `change_source`) as well as `subscriber.updated`.
|
|
750
|
+
|
|
751
|
+
---
|
|
752
|
+
|
|
715
753
|
## Segments
|
|
716
754
|
|
|
717
755
|
Define subscriber groups using rules for targeted broadcasts and sequence enrollment.
|
|
@@ -857,6 +895,11 @@ result = client.opt_in_forms.create(
|
|
|
857
895
|
# Update (deeply nested settings hashes pass through verbatim)
|
|
858
896
|
client.opt_in_forms.update(1, enabled: false)
|
|
859
897
|
|
|
898
|
+
# How often the same visitor sees a popup. Any word outside
|
|
899
|
+
# Broadcast::Resources::OptInForms::TRIGGER_FREQUENCIES raises
|
|
900
|
+
# Broadcast::ValidationError (422).
|
|
901
|
+
client.opt_in_forms.update(1, trigger_settings: { frequency: 'once_per_day' })
|
|
902
|
+
|
|
860
903
|
# Delete
|
|
861
904
|
client.opt_in_forms.delete(1)
|
|
862
905
|
```
|
|
@@ -1131,7 +1174,7 @@ secret = result['secret']
|
|
|
1131
1174
|
|
|
1132
1175
|
# Every valid event type is available as a constant. An unknown event type is
|
|
1133
1176
|
# dropped server-side rather than rejected, so subscribe from these.
|
|
1134
|
-
Broadcast::Webhook::EVENT_TYPES # all
|
|
1177
|
+
Broadcast::Webhook::EVENT_TYPES # all 37
|
|
1135
1178
|
Broadcast::Webhook::EMAIL_EVENTS # email.sent, email.delivered, ...
|
|
1136
1179
|
Broadcast::Webhook::SUBSCRIBER_EVENTS # subscriber.created, ..., subscribers.purged
|
|
1137
1180
|
Broadcast::Webhook::BROADCAST_EVENTS # broadcast.sending, broadcast.sent, ...
|
|
@@ -1162,9 +1205,9 @@ result['data'] # => [{'id' => 1, 'event_type' => 'email.sent', 'response_status
|
|
|
1162
1205
|
|
|
1163
1206
|
| Category | Events |
|
|
1164
1207
|
|----------|--------|
|
|
1165
|
-
| Email | `email.sent`, `email.delivered`, `email.delivery_delayed`, `email.opened`, `email.clicked`, `email.bounced`, `email.complained`, `email.failed` |
|
|
1208
|
+
| Email | `email.sent`, `email.delivered`, `email.delivery_delayed`, `email.send_delayed`, `email.opened`, `email.clicked`, `email.bounced`, `email.complained`, `email.failed` |
|
|
1166
1209
|
| Subscriber | `subscriber.created`, `subscriber.updated`, `subscriber.deleted`, `subscriber.subscribed`, `subscriber.unsubscribed`, `subscriber.bounced`, `subscriber.complained`, `subscribers.purged`, `subscribers.purge_failed` |
|
|
1167
|
-
| Broadcast | `broadcast.scheduled`, `broadcast.queueing`, `broadcast.sending`, `broadcast.sent`, `broadcast.failed`, `broadcast.partial_failure`, `broadcast.aborted`, `broadcast.paused` |
|
|
1210
|
+
| Broadcast | `broadcast.scheduled`, `broadcast.queueing`, `broadcast.sending`, `broadcast.sent`, `broadcast.failed`, `broadcast.partial_failure`, `broadcast.aborted`, `broadcast.paused`, `broadcast.batch_completed` |
|
|
1168
1211
|
| Sequence | `sequence.subscriber_added`, `sequence.subscriber_completed`, `sequence.subscriber_moved`, `sequence.subscriber_removed`, `sequence.subscriber_paused`, `sequence.subscriber_resumed`, `sequence.subscriber_error` |
|
|
1169
1212
|
| System | `message.attempt.exhausted`, `test.webhook` |
|
|
1170
1213
|
|
data/SDK-COVERAGE.md
CHANGED
|
@@ -331,7 +331,7 @@ Inbound webhooks — the app calling you.
|
|
|
331
331
|
| HMAC-SHA256 verification (`v1,<base64>`) | ✅ `Webhook.verify` |
|
|
332
332
|
| 5-minute timestamp tolerance | ✅ Replay protection |
|
|
333
333
|
| Constant-time comparison | ✅ `OpenSSL.fixed_length_secure_compare` |
|
|
334
|
-
| Event type constants | ✅ `Webhook::EVENT_TYPES` —
|
|
334
|
+
| Event type constants | ✅ `Webhook::EVENT_TYPES` — 37 values |
|
|
335
335
|
|
|
336
336
|
Categories: `EMAIL_EVENTS` (8), `SUBSCRIBER_EVENTS` (7), `BROADCAST_EVENTS` (8),
|
|
337
337
|
`SEQUENCE_EVENTS` (7), `SYSTEM_EVENTS` (2).
|
data/lib/broadcast/client.rb
CHANGED
|
@@ -30,6 +30,7 @@ module Broadcast
|
|
|
30
30
|
# copies_to_generate: how many drafts each run produces
|
|
31
31
|
# tone_description:, content_instructions:, newsletter_structure:
|
|
32
32
|
# segment_ids: array — restrict the newsletter's audience
|
|
33
|
+
# excluded_segment_ids: array — segments its drafts never reach
|
|
33
34
|
def create(**attrs)
|
|
34
35
|
post('/api/v1/autopilots', { autopilot: attrs })
|
|
35
36
|
end
|
|
@@ -5,6 +5,12 @@ require 'date'
|
|
|
5
5
|
module Broadcast
|
|
6
6
|
module Resources
|
|
7
7
|
class OptInForms < Base
|
|
8
|
+
# The words trigger_settings[:frequency] accepts: how often the same visitor
|
|
9
|
+
# sees a popup. always and every_visit mean the same. The server refuses any
|
|
10
|
+
# other word with 422 (Broadcast::ValidationError); the client does not
|
|
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
|
|
13
|
+
|
|
8
14
|
# List opt-in forms.
|
|
9
15
|
#
|
|
10
16
|
# NOTE: returns up to 250 results per page along with `pagination`
|
|
@@ -36,6 +42,10 @@ module Broadcast
|
|
|
36
42
|
#
|
|
37
43
|
# Custom fields declared in opt_in_form_blocks_attributes support a
|
|
38
44
|
# multi-line variant, which renders as a textarea on the hosted form.
|
|
45
|
+
#
|
|
46
|
+
# trigger_settings: { frequency: } must be one of TRIGGER_FREQUENCIES;
|
|
47
|
+
# any other word raises Broadcast::ValidationError (422). Keys sent in a
|
|
48
|
+
# settings hash are merged into the stored settings.
|
|
39
49
|
def create(**attrs)
|
|
40
50
|
post('/api/v1/opt_in_forms', { opt_in_form: attrs })
|
|
41
51
|
end
|
|
@@ -45,7 +45,13 @@ module Broadcast
|
|
|
45
45
|
# another provider. Ignored (with a warning) on update,
|
|
46
46
|
# and ignored entirely for channel-scoped tokens.
|
|
47
47
|
#
|
|
48
|
-
#
|
|
48
|
+
# Any token, create only:
|
|
49
|
+
# unsubscribed_at: ISO 8601. Stores the subscriber as unsubscribed in the
|
|
50
|
+
# same request (inactive, on the unsubscribe list), for
|
|
51
|
+
# migrating a list with its opt-outs intact. No
|
|
52
|
+
# confirmation email; 422 if it is in the future or sent
|
|
53
|
+
# with is_active: true. To unsubscribe an existing
|
|
54
|
+
# subscriber, use `unsubscribe(email)`.
|
|
49
55
|
def create(**attrs)
|
|
50
56
|
double_opt_in = attrs.delete(:double_opt_in)
|
|
51
57
|
confirmation_template_id = attrs.delete(:confirmation_template_id)
|
|
@@ -57,8 +63,12 @@ module Broadcast
|
|
|
57
63
|
post('/api/v1/subscribers.json', payload)
|
|
58
64
|
end
|
|
59
65
|
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
# custom_data_mode: 'replace' (the API default) stores custom_data as sent;
|
|
67
|
+
# 'merge' changes only the keys sent, and a nil value deletes that key.
|
|
68
|
+
def update(email, custom_data_mode: nil, **attrs)
|
|
69
|
+
body = { email: email, subscriber: attrs }
|
|
70
|
+
body[:custom_data_mode] = custom_data_mode if custom_data_mode
|
|
71
|
+
patch('/api/v1/subscribers.json', body)
|
|
62
72
|
end
|
|
63
73
|
|
|
64
74
|
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
|
data/lib/broadcast/version.rb
CHANGED
data/lib/broadcast/webhook.rb
CHANGED
|
@@ -10,15 +10,18 @@ module Broadcast
|
|
|
10
10
|
# Every event type a webhook endpoint can subscribe to, mirroring
|
|
11
11
|
# WebhookEndpoint::AVAILABLE_EVENT_TYPES server-side. Use these when
|
|
12
12
|
# creating an endpoint — an unknown event type is dropped silently.
|
|
13
|
+
# email.send_delayed: a transactional email is waiting for room under its
|
|
14
|
+
# server's hourly limit (not yet sent). email.delivery_delayed: the
|
|
15
|
+
# provider accepted it and the recipient's server is delaying it.
|
|
13
16
|
EMAIL_EVENTS = %w[
|
|
14
|
-
email.sent email.delivered email.delivery_delayed email.
|
|
15
|
-
email.bounced email.opened email.clicked email.failed
|
|
17
|
+
email.sent email.delivered email.delivery_delayed email.send_delayed
|
|
18
|
+
email.complained email.bounced email.opened email.clicked email.failed
|
|
16
19
|
].freeze
|
|
17
20
|
|
|
18
21
|
# subscribers.* are sent once for a whole-list purge, in place of a
|
|
19
22
|
# subscriber.deleted per row.
|
|
20
23
|
SUBSCRIBER_EVENTS = %w[
|
|
21
|
-
subscriber.created subscriber.updated subscriber.deleted
|
|
24
|
+
subscriber.created subscriber.updated subscriber.preferences_updated subscriber.deleted
|
|
22
25
|
subscriber.subscribed subscriber.unsubscribed subscriber.bounced
|
|
23
26
|
subscriber.complained subscribers.purged subscribers.purge_failed
|
|
24
27
|
].freeze
|
|
@@ -26,7 +29,7 @@ module Broadcast
|
|
|
26
29
|
BROADCAST_EVENTS = %w[
|
|
27
30
|
broadcast.scheduled broadcast.queueing broadcast.sending broadcast.sent
|
|
28
31
|
broadcast.failed broadcast.partial_failure broadcast.aborted
|
|
29
|
-
broadcast.paused
|
|
32
|
+
broadcast.paused broadcast.batch_completed
|
|
30
33
|
].freeze
|
|
31
34
|
|
|
32
35
|
SEQUENCE_EVENTS = %w[
|
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.
|
|
4
|
+
version: 0.8.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
|
|
@@ -90,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
90
91
|
- !ruby/object:Gem::Version
|
|
91
92
|
version: '0'
|
|
92
93
|
requirements: []
|
|
93
|
-
rubygems_version: 4.0.
|
|
94
|
+
rubygems_version: 4.0.16
|
|
94
95
|
specification_version: 4
|
|
95
96
|
summary: Ruby client for the Broadcast email platform
|
|
96
97
|
test_files: []
|