broadcast-ruby 0.1.0 → 0.1.1
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/Gemfile +2 -0
- data/Gemfile.lock +126 -1
- data/README.md +205 -19
- data/lib/broadcast/delivery_method.rb +32 -0
- data/lib/broadcast/errors.rb +2 -0
- data/lib/broadcast/railtie.rb +13 -0
- data/lib/broadcast/version.rb +1 -1
- data/lib/broadcast.rb +6 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8f76251ad3cf3d351bd4b197ccc5b4035af1eea66290380050c6f5108ec48b6a
|
|
4
|
+
data.tar.gz: 85c3e88178863291ab1da149797f71ea36a7eeaf3418bb72bdcf95ab92dfcf7e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5d71f0add4f5ae7b00705753069c6eee46831c2a1a9b7af36f7dc7cd27d522100d471923a3450a2cd5bab6897b567ace37c127d8e9df16e7e9cef57f53f5a55e
|
|
7
|
+
data.tar.gz: f31f85327c7c231c42d5134961387b5da21d2cc6a9080ca8d31061d5be540a4c38f6e4a9641a4c66427767a2ba24129d746f5ba0ebcebf5a4f320b0d12ea29ca
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -7,24 +7,96 @@ PATH
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
+
actionmailer (8.1.2)
|
|
11
|
+
actionpack (= 8.1.2)
|
|
12
|
+
actionview (= 8.1.2)
|
|
13
|
+
activejob (= 8.1.2)
|
|
14
|
+
activesupport (= 8.1.2)
|
|
15
|
+
mail (>= 2.8.0)
|
|
16
|
+
rails-dom-testing (~> 2.2)
|
|
17
|
+
actionpack (8.1.2)
|
|
18
|
+
actionview (= 8.1.2)
|
|
19
|
+
activesupport (= 8.1.2)
|
|
20
|
+
nokogiri (>= 1.8.5)
|
|
21
|
+
rack (>= 2.2.4)
|
|
22
|
+
rack-session (>= 1.0.1)
|
|
23
|
+
rack-test (>= 0.6.3)
|
|
24
|
+
rails-dom-testing (~> 2.2)
|
|
25
|
+
rails-html-sanitizer (~> 1.6)
|
|
26
|
+
useragent (~> 0.16)
|
|
27
|
+
actionview (8.1.2)
|
|
28
|
+
activesupport (= 8.1.2)
|
|
29
|
+
builder (~> 3.1)
|
|
30
|
+
erubi (~> 1.11)
|
|
31
|
+
rails-dom-testing (~> 2.2)
|
|
32
|
+
rails-html-sanitizer (~> 1.6)
|
|
33
|
+
activejob (8.1.2)
|
|
34
|
+
activesupport (= 8.1.2)
|
|
35
|
+
globalid (>= 0.3.6)
|
|
36
|
+
activesupport (8.1.2)
|
|
37
|
+
base64
|
|
38
|
+
bigdecimal
|
|
39
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
40
|
+
connection_pool (>= 2.2.5)
|
|
41
|
+
drb
|
|
42
|
+
i18n (>= 1.6, < 2)
|
|
43
|
+
json
|
|
44
|
+
logger (>= 1.4.2)
|
|
45
|
+
minitest (>= 5.1)
|
|
46
|
+
securerandom (>= 0.3)
|
|
47
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
48
|
+
uri (>= 0.13.1)
|
|
10
49
|
addressable (2.8.9)
|
|
11
50
|
public_suffix (>= 2.0.2, < 8.0)
|
|
12
51
|
ast (2.4.3)
|
|
13
52
|
base64 (0.3.0)
|
|
14
53
|
bigdecimal (4.0.1)
|
|
54
|
+
builder (3.3.0)
|
|
55
|
+
concurrent-ruby (1.3.6)
|
|
56
|
+
connection_pool (3.0.2)
|
|
15
57
|
crack (1.0.1)
|
|
16
58
|
bigdecimal
|
|
17
59
|
rexml
|
|
60
|
+
crass (1.0.6)
|
|
61
|
+
date (3.5.1)
|
|
62
|
+
drb (2.2.3)
|
|
63
|
+
erubi (1.13.1)
|
|
64
|
+
globalid (1.3.0)
|
|
65
|
+
activesupport (>= 6.1)
|
|
18
66
|
hashdiff (1.2.1)
|
|
67
|
+
i18n (1.14.8)
|
|
68
|
+
concurrent-ruby (~> 1.0)
|
|
19
69
|
json (2.19.1)
|
|
20
70
|
json-schema (6.2.0)
|
|
21
71
|
addressable (~> 2.8)
|
|
22
72
|
bigdecimal (>= 3.1, < 5)
|
|
23
73
|
language_server-protocol (3.17.0.5)
|
|
24
74
|
lint_roller (1.1.0)
|
|
75
|
+
logger (1.7.0)
|
|
76
|
+
loofah (2.25.1)
|
|
77
|
+
crass (~> 1.0.2)
|
|
78
|
+
nokogiri (>= 1.12.0)
|
|
79
|
+
mail (2.9.0)
|
|
80
|
+
logger
|
|
81
|
+
mini_mime (>= 0.1.1)
|
|
82
|
+
net-imap
|
|
83
|
+
net-pop
|
|
84
|
+
net-smtp
|
|
25
85
|
mcp (0.8.0)
|
|
26
86
|
json-schema (>= 4.1)
|
|
87
|
+
mini_mime (1.1.5)
|
|
27
88
|
minitest (5.27.0)
|
|
89
|
+
net-imap (0.6.3)
|
|
90
|
+
date
|
|
91
|
+
net-protocol
|
|
92
|
+
net-pop (0.1.2)
|
|
93
|
+
net-protocol
|
|
94
|
+
net-protocol (0.2.2)
|
|
95
|
+
timeout
|
|
96
|
+
net-smtp (0.5.1)
|
|
97
|
+
net-protocol
|
|
98
|
+
nokogiri (1.19.1-arm64-darwin)
|
|
99
|
+
racc (~> 1.4)
|
|
28
100
|
parallel (1.27.0)
|
|
29
101
|
parser (3.3.10.2)
|
|
30
102
|
ast (~> 2.4.1)
|
|
@@ -32,6 +104,19 @@ GEM
|
|
|
32
104
|
prism (1.9.0)
|
|
33
105
|
public_suffix (7.0.5)
|
|
34
106
|
racc (1.8.1)
|
|
107
|
+
rack (3.2.5)
|
|
108
|
+
rack-session (2.1.1)
|
|
109
|
+
base64 (>= 0.1.0)
|
|
110
|
+
rack (>= 3.0.0)
|
|
111
|
+
rack-test (2.2.0)
|
|
112
|
+
rack (>= 1.3)
|
|
113
|
+
rails-dom-testing (2.3.0)
|
|
114
|
+
activesupport (>= 5.0.0)
|
|
115
|
+
minitest
|
|
116
|
+
nokogiri (>= 1.6)
|
|
117
|
+
rails-html-sanitizer (1.7.0)
|
|
118
|
+
loofah (~> 2.25)
|
|
119
|
+
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
|
35
120
|
rainbow (3.1.1)
|
|
36
121
|
rake (13.3.1)
|
|
37
122
|
regexp_parser (2.11.3)
|
|
@@ -52,9 +137,15 @@ GEM
|
|
|
52
137
|
parser (>= 3.3.7.2)
|
|
53
138
|
prism (~> 1.7)
|
|
54
139
|
ruby-progressbar (1.13.0)
|
|
140
|
+
securerandom (0.4.1)
|
|
141
|
+
timeout (0.6.1)
|
|
142
|
+
tzinfo (2.0.6)
|
|
143
|
+
concurrent-ruby (~> 1.0)
|
|
55
144
|
unicode-display_width (3.2.0)
|
|
56
145
|
unicode-emoji (~> 4.1)
|
|
57
146
|
unicode-emoji (4.2.0)
|
|
147
|
+
uri (1.1.1)
|
|
148
|
+
useragent (0.16.11)
|
|
58
149
|
webmock (3.26.1)
|
|
59
150
|
addressable (>= 2.8.0)
|
|
60
151
|
crack (>= 0.3.2)
|
|
@@ -62,34 +153,63 @@ GEM
|
|
|
62
153
|
|
|
63
154
|
PLATFORMS
|
|
64
155
|
arm64-darwin-24
|
|
65
|
-
ruby
|
|
66
156
|
|
|
67
157
|
DEPENDENCIES
|
|
158
|
+
actionmailer (>= 6.0)
|
|
68
159
|
broadcast-ruby!
|
|
160
|
+
mail (~> 2.8)
|
|
69
161
|
minitest (~> 5.0)
|
|
70
162
|
rake (~> 13.0)
|
|
71
163
|
rubocop (~> 1.0)
|
|
72
164
|
webmock (~> 3.0)
|
|
73
165
|
|
|
74
166
|
CHECKSUMS
|
|
167
|
+
actionmailer (8.1.2) sha256=f4c1d2060f653bfe908aa7fdc5a61c0e5279670de992146582f2e36f8b9175e9
|
|
168
|
+
actionpack (8.1.2) sha256=ced74147a1f0daafaa4bab7f677513fd4d3add574c7839958f7b4f1de44f8423
|
|
169
|
+
actionview (8.1.2) sha256=80455b2588911c9b72cec22d240edacb7c150e800ef2234821269b2b2c3e2e5b
|
|
170
|
+
activejob (8.1.2) sha256=908dab3713b101859536375819f4156b07bdf4c232cc645e7538adb9e302f825
|
|
171
|
+
activesupport (8.1.2) sha256=88842578ccd0d40f658289b0e8c842acfe9af751afee2e0744a7873f50b6fdae
|
|
75
172
|
addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485
|
|
76
173
|
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
|
77
174
|
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
78
175
|
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
|
|
79
176
|
broadcast-ruby (0.1.0)
|
|
177
|
+
builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
|
|
178
|
+
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
|
|
179
|
+
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
|
|
80
180
|
crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e
|
|
181
|
+
crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d
|
|
182
|
+
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
|
|
183
|
+
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
|
184
|
+
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
|
|
185
|
+
globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11
|
|
81
186
|
hashdiff (1.2.1) sha256=9c079dbc513dfc8833ab59c0c2d8f230fa28499cc5efb4b8dd276cf931457cd1
|
|
187
|
+
i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
|
|
82
188
|
json (2.19.1) sha256=dd94fdc59e48bff85913829a32350b3148156bc4fd2a95a2568a78b11344082d
|
|
83
189
|
json-schema (6.2.0) sha256=e8bff46ed845a22c1ab2bd0d7eccf831c01fe23bb3920caa4c74db4306813666
|
|
84
190
|
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
|
|
85
191
|
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
192
|
+
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
193
|
+
loofah (2.25.1) sha256=d436c73dbd0c1147b16c4a41db097942d217303e1f7728704b37e4df9f6d2e04
|
|
194
|
+
mail (2.9.0) sha256=6fa6673ecd71c60c2d996260f9ee3dd387d4673b8169b502134659ece6d34941
|
|
86
195
|
mcp (0.8.0) sha256=ae8bd146bb8e168852866fd26f805f52744f6326afb3211e073f78a95e0c34fb
|
|
196
|
+
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
|
|
87
197
|
minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
|
|
198
|
+
net-imap (0.6.3) sha256=9bab75f876596d09ee7bf911a291da478e0cd6badc54dfb82874855ccc82f2ad
|
|
199
|
+
net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3
|
|
200
|
+
net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8
|
|
201
|
+
net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736
|
|
202
|
+
nokogiri (1.19.1-arm64-darwin) sha256=dfe2d337e6700eac47290407c289d56bcf85805d128c1b5a6434ddb79731cb9e
|
|
88
203
|
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
|
|
89
204
|
parser (3.3.10.2) sha256=6f60c84aa4bdcedb6d1a2434b738fe8a8136807b6adc8f7f53b97da9bc4e9357
|
|
90
205
|
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
|
|
91
206
|
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
|
|
92
207
|
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
208
|
+
rack (3.2.5) sha256=4cbd0974c0b79f7a139b4812004a62e4c60b145cba76422e288ee670601ed6d3
|
|
209
|
+
rack-session (2.1.1) sha256=0b6dc07dea7e4b583f58a48e8b806d4c9f1c6c9214ebc202ec94562cbea2e4e9
|
|
210
|
+
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
|
|
211
|
+
rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d
|
|
212
|
+
rails-html-sanitizer (1.7.0) sha256=28b145cceaf9cc214a9874feaa183c3acba036c9592b19886e0e45efc62b1e89
|
|
93
213
|
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
|
94
214
|
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
|
|
95
215
|
regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4
|
|
@@ -97,8 +217,13 @@ CHECKSUMS
|
|
|
97
217
|
rubocop (1.85.1) sha256=3dbcf9e961baa4c376eeeb2a03913dca5e3987033b04d38fa538aa1e7406cc77
|
|
98
218
|
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
|
|
99
219
|
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
|
220
|
+
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
|
|
221
|
+
timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb
|
|
222
|
+
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
|
|
100
223
|
unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
|
|
101
224
|
unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
|
|
225
|
+
uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
|
|
226
|
+
useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844
|
|
102
227
|
webmock (3.26.1) sha256=4f696fb57c90a827c20aadb2d4f9058bbff10f7f043bd0d4c3f58791143b1cd7
|
|
103
228
|
|
|
104
229
|
BUNDLED WITH
|
data/README.md
CHANGED
|
@@ -6,13 +6,35 @@ Works with [sendbroadcast.com](https://sendbroadcast.com) or any self-hosted Bro
|
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
+
Add to your Gemfile:
|
|
10
|
+
|
|
9
11
|
```ruby
|
|
10
12
|
gem 'broadcast-ruby'
|
|
11
13
|
```
|
|
12
14
|
|
|
15
|
+
For plain Ruby scripts (non-Rails):
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
require 'broadcast'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Getting Your API Token
|
|
22
|
+
|
|
23
|
+
1. Log in to your Broadcast dashboard
|
|
24
|
+
2. Go to **Settings > API Keys**
|
|
25
|
+
3. Click **New API Key**
|
|
26
|
+
4. Name it, select the permissions you need (see [Permissions](#api-token-permissions) below), and save
|
|
27
|
+
5. Copy the token
|
|
28
|
+
|
|
13
29
|
## Quick Start
|
|
14
30
|
|
|
31
|
+
**In a Rails app?** Jump to [Rails ActionMailer Integration](#rails-actionmailer-integration) -- you don't need to use the client directly.
|
|
32
|
+
|
|
33
|
+
**In a plain Ruby script or non-Rails app?** Use the client:
|
|
34
|
+
|
|
15
35
|
```ruby
|
|
36
|
+
require 'broadcast'
|
|
37
|
+
|
|
16
38
|
client = Broadcast::Client.new(
|
|
17
39
|
api_token: 'your-token',
|
|
18
40
|
host: 'https://sendbroadcast.com' # or your self-hosted URL
|
|
@@ -44,11 +66,91 @@ All methods return parsed JSON as Ruby Hashes with string keys.
|
|
|
44
66
|
|
|
45
67
|
---
|
|
46
68
|
|
|
69
|
+
## Rails ActionMailer Integration
|
|
70
|
+
|
|
71
|
+
In a Rails app, the gem auto-registers a `:broadcast` delivery method. Your existing mailers work unchanged.
|
|
72
|
+
|
|
73
|
+
First, store your API token in Rails credentials:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
bin/rails credentials:edit
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Add:
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
broadcast:
|
|
83
|
+
api_token: your-token-here
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Then configure production to use Broadcast:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
# config/environments/production.rb
|
|
90
|
+
config.action_mailer.delivery_method = :broadcast
|
|
91
|
+
config.action_mailer.broadcast_settings = {
|
|
92
|
+
api_token: Rails.application.credentials.dig(:broadcast, :api_token),
|
|
93
|
+
host: 'https://sendbroadcast.com'
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
All your mailers just work:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
UserMailer.welcome(user).deliver_later
|
|
101
|
+
PasswordsMailer.reset(user).deliver_now
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The delivery method extracts the HTML body (falling back to text), subject, recipient, and reply-to from the rendered email and sends it via Broadcast's transactional API. Delivery errors raise `Broadcast::DeliveryError`, which lets Active Job (Solid Queue, Sidekiq, etc.) retry automatically.
|
|
105
|
+
|
|
106
|
+
Development and test environments are unaffected:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
# config/environments/development.rb
|
|
110
|
+
config.action_mailer.delivery_method = :letter_opener
|
|
111
|
+
|
|
112
|
+
# config/environments/test.rb
|
|
113
|
+
config.action_mailer.delivery_method = :test
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Migrating from Postmark
|
|
117
|
+
|
|
118
|
+
Replace in your Gemfile:
|
|
119
|
+
|
|
120
|
+
```diff
|
|
121
|
+
- gem 'postmark-rails'
|
|
122
|
+
+ gem 'broadcast-ruby'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Replace in `config/environments/production.rb`:
|
|
126
|
+
|
|
127
|
+
```diff
|
|
128
|
+
- config.action_mailer.delivery_method = :postmark
|
|
129
|
+
- config.action_mailer.postmark_settings = {
|
|
130
|
+
- api_token: Rails.application.credentials.dig(:postmark, :api_token)
|
|
131
|
+
- }
|
|
132
|
+
+ config.action_mailer.delivery_method = :broadcast
|
|
133
|
+
+ config.action_mailer.broadcast_settings = {
|
|
134
|
+
+ api_token: Rails.application.credentials.dig(:broadcast, :api_token),
|
|
135
|
+
+ host: 'https://sendbroadcast.com'
|
|
136
|
+
+ }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Your mailer classes, views, and tests stay exactly the same.
|
|
140
|
+
|
|
141
|
+
### Migrating from SendGrid / Mailgun
|
|
142
|
+
|
|
143
|
+
Same pattern -- swap the gem and the delivery config. No changes to mailers.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
47
147
|
## Transactional Email
|
|
48
148
|
|
|
49
149
|
Send one-off emails triggered by application events. Transactional emails bypass unsubscribe status (for password resets, receipts, order confirmations, etc.).
|
|
50
150
|
|
|
51
|
-
The sender address is configured at the channel level in your Broadcast instance
|
|
151
|
+
The sender address is configured at the channel level in your Broadcast instance -- there is no `from` parameter.
|
|
152
|
+
|
|
153
|
+
**Required permissions:** `transactionals_read`, `transactionals_write`
|
|
52
154
|
|
|
53
155
|
```ruby
|
|
54
156
|
# Send an email
|
|
@@ -86,6 +188,8 @@ email['queue_at'] # => '2026-03-17T07:59:58Z'
|
|
|
86
188
|
|
|
87
189
|
Manage your contact list. Subscribers can have tags and custom data fields for segmentation and personalization.
|
|
88
190
|
|
|
191
|
+
**Required permissions:** `subscribers_read`, `subscribers_write`
|
|
192
|
+
|
|
89
193
|
```ruby
|
|
90
194
|
# List subscribers (paginated, 250 per page)
|
|
91
195
|
result = client.subscribers.list(page: 1)
|
|
@@ -154,7 +258,7 @@ client.subscribers.unsubscribe('jane@example.com')
|
|
|
154
258
|
# Resubscribe (clears unsubscribed status AND activates)
|
|
155
259
|
client.subscribers.resubscribe('jane@example.com')
|
|
156
260
|
|
|
157
|
-
# Redact
|
|
261
|
+
# Redact -- GDPR "right to be forgotten" (irreversible)
|
|
158
262
|
# Removes PII but preserves aggregate campaign statistics
|
|
159
263
|
client.subscribers.redact('jane@example.com')
|
|
160
264
|
```
|
|
@@ -165,6 +269,8 @@ client.subscribers.redact('jane@example.com')
|
|
|
165
269
|
|
|
166
270
|
Automated drip campaigns. Add subscribers to a sequence and they flow through the steps automatically.
|
|
167
271
|
|
|
272
|
+
**Required permissions:** `sequences_read`, `sequences_write` (also `subscribers_read`, `subscribers_write` for enrollment)
|
|
273
|
+
|
|
168
274
|
```ruby
|
|
169
275
|
# List all sequences
|
|
170
276
|
result = client.sequences.list
|
|
@@ -230,7 +336,7 @@ client.sequences.list_steps(sequence_id)
|
|
|
230
336
|
# Get a single step
|
|
231
337
|
step = client.sequences.get_step(sequence_id, step_id)
|
|
232
338
|
|
|
233
|
-
# Create steps
|
|
339
|
+
# Create steps -- each step needs an action, label, and parent_id
|
|
234
340
|
# The parent_id links steps into a tree (entry point is the sequence's root step)
|
|
235
341
|
|
|
236
342
|
# Delay step (seconds)
|
|
@@ -279,6 +385,8 @@ client.sequences.delete_step(sequence_id, step_id)
|
|
|
279
385
|
|
|
280
386
|
One-time email campaigns sent to your subscriber list or targeted segments.
|
|
281
387
|
|
|
388
|
+
**Required permissions:** `broadcasts_read`, `broadcasts_write`
|
|
389
|
+
|
|
282
390
|
```ruby
|
|
283
391
|
# List broadcasts
|
|
284
392
|
result = client.broadcasts.list(limit: 10, offset: 0)
|
|
@@ -350,6 +458,8 @@ client.broadcasts.statistics_links(1, sort: 'clicks', order: 'desc')
|
|
|
350
458
|
|
|
351
459
|
Define subscriber groups using rules for targeted broadcasts and sequence enrollment.
|
|
352
460
|
|
|
461
|
+
**Required permissions:** `segments_read`, `segments_write`
|
|
462
|
+
|
|
353
463
|
```ruby
|
|
354
464
|
# List segments
|
|
355
465
|
result = client.segments.list
|
|
@@ -401,6 +511,8 @@ client.segments.delete(1)
|
|
|
401
511
|
|
|
402
512
|
Reusable email templates with [Liquid](https://shopify.github.io/liquid/) variable support for personalization (e.g. `{{first_name}}`, `{{email}}`).
|
|
403
513
|
|
|
514
|
+
**Required permissions:** `templates_read`, `templates_write`
|
|
515
|
+
|
|
404
516
|
```ruby
|
|
405
517
|
# List all templates
|
|
406
518
|
result = client.templates.list
|
|
@@ -435,6 +547,8 @@ client.templates.delete(1)
|
|
|
435
547
|
|
|
436
548
|
Receive real-time notifications when events occur (email delivered, subscriber created, sequence completed, etc.).
|
|
437
549
|
|
|
550
|
+
**Required permissions:** `webhook_endpoints_read`, `webhook_endpoints_write`
|
|
551
|
+
|
|
438
552
|
```ruby
|
|
439
553
|
# List endpoints
|
|
440
554
|
result = client.webhook_endpoints.list
|
|
@@ -453,10 +567,10 @@ result = client.webhook_endpoints.create(
|
|
|
453
567
|
],
|
|
454
568
|
retries_to_attempt: 6
|
|
455
569
|
)
|
|
456
|
-
# IMPORTANT: Save the secret from the response
|
|
570
|
+
# IMPORTANT: Save the secret from the response -- it is only shown once
|
|
457
571
|
secret = result['secret']
|
|
458
572
|
|
|
459
|
-
# Update (url and secret cannot be changed
|
|
573
|
+
# Update (url and secret cannot be changed -- create a new endpoint instead)
|
|
460
574
|
client.webhook_endpoints.update(1, active: false)
|
|
461
575
|
client.webhook_endpoints.update(1, event_types: ['email.delivered', 'email.opened'])
|
|
462
576
|
|
|
@@ -483,16 +597,35 @@ result['data'] # => [{'id' => 1, 'event_type' => 'email.sent', 'response_status
|
|
|
483
597
|
|
|
484
598
|
### Webhook Signature Verification
|
|
485
599
|
|
|
486
|
-
All incoming webhooks are signed with HMAC-SHA256.
|
|
600
|
+
All incoming webhooks are signed with HMAC-SHA256. Here's a complete Rails controller:
|
|
487
601
|
|
|
488
602
|
```ruby
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
603
|
+
class WebhooksController < ApplicationController
|
|
604
|
+
skip_before_action :verify_authenticity_token
|
|
605
|
+
|
|
606
|
+
def create
|
|
607
|
+
payload = request.raw_post
|
|
608
|
+
signature = request.headers['broadcast-webhook-signature']
|
|
609
|
+
timestamp = request.headers['broadcast-webhook-timestamp']
|
|
610
|
+
|
|
611
|
+
unless Broadcast::Webhook.verify(payload, signature, timestamp,
|
|
612
|
+
secret: ENV['BROADCAST_WEBHOOK_SECRET'])
|
|
613
|
+
head :unauthorized
|
|
614
|
+
return
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
event = JSON.parse(payload)
|
|
618
|
+
|
|
619
|
+
case event['type']
|
|
620
|
+
when 'email.delivered'
|
|
621
|
+
# handle delivery
|
|
622
|
+
when 'subscriber.unsubscribed'
|
|
623
|
+
# handle unsubscribe
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
head :ok
|
|
627
|
+
end
|
|
628
|
+
end
|
|
496
629
|
```
|
|
497
630
|
|
|
498
631
|
The signature is computed as `HMAC-SHA256(timestamp + "." + payload, secret)`. Timestamps older than 5 minutes are rejected to prevent replay attacks.
|
|
@@ -501,21 +634,74 @@ The signature is computed as `HMAC-SHA256(timestamp + "." + payload, secret)`. T
|
|
|
501
634
|
|
|
502
635
|
## Error Handling
|
|
503
636
|
|
|
504
|
-
All API errors inherit from `Broadcast::Error
|
|
637
|
+
All API errors inherit from `Broadcast::Error`. Put specific errors before general ones:
|
|
505
638
|
|
|
506
639
|
```ruby
|
|
507
640
|
begin
|
|
508
641
|
client.send_email(to: 'user@example.com', subject: 'Hi', body: 'Hello')
|
|
509
|
-
rescue Broadcast::AuthenticationError # 401
|
|
510
|
-
rescue Broadcast::NotFoundError # 404
|
|
511
|
-
rescue Broadcast::ValidationError # 422
|
|
512
|
-
rescue Broadcast::RateLimitError # 429
|
|
642
|
+
rescue Broadcast::AuthenticationError # 401 -- invalid or expired API token
|
|
643
|
+
rescue Broadcast::NotFoundError # 404 -- resource does not exist
|
|
644
|
+
rescue Broadcast::ValidationError # 422 -- missing or invalid parameters
|
|
645
|
+
rescue Broadcast::RateLimitError # 429 -- exceeded 120 requests/minute
|
|
513
646
|
rescue Broadcast::TimeoutError # connection or read timeout
|
|
514
647
|
rescue Broadcast::APIError # 5xx or unexpected status codes
|
|
648
|
+
rescue Broadcast::DeliveryError # ActionMailer wrapper (wraps any of the above)
|
|
515
649
|
end
|
|
516
650
|
```
|
|
517
651
|
|
|
518
|
-
Server errors (5xx) and timeouts are automatically retried with linear backoff. Client errors (401, 404, 422, 429) are raised immediately.
|
|
652
|
+
Server errors (5xx) and timeouts are automatically retried with linear backoff. Client errors (401, 404, 422, 429) are raised immediately. `DeliveryError` is only raised from the ActionMailer delivery method -- it wraps the underlying API error.
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## API Token Permissions
|
|
657
|
+
|
|
658
|
+
Each token can be scoped to specific resources. The ActionMailer delivery method only needs transactional permissions. Use the minimum permissions your integration requires.
|
|
659
|
+
|
|
660
|
+
| Resource | Read permission | Write permission |
|
|
661
|
+
|----------|----------------|------------------|
|
|
662
|
+
| Transactional Emails | `transactionals_read` -- get delivery status | `transactionals_write` -- send emails |
|
|
663
|
+
| Subscribers | `subscribers_read` -- list, find | `subscribers_write` -- create, update, tag, deactivate, unsubscribe, redact |
|
|
664
|
+
| Sequences | `sequences_read` -- list, get, list steps | `sequences_write` -- create, update, delete, manage steps, enroll subscribers |
|
|
665
|
+
| Broadcasts | `broadcasts_read` -- list, get, statistics | `broadcasts_write` -- create, update, delete, send, schedule |
|
|
666
|
+
| Segments | `segments_read` -- list, get | `segments_write` -- create, update, delete |
|
|
667
|
+
| Templates | `templates_read` -- list, get | `templates_write` -- create, update, delete |
|
|
668
|
+
| Webhook Endpoints | `webhook_endpoints_read` -- list, get, deliveries | `webhook_endpoints_write` -- create, update, delete, test |
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
672
|
+
## Troubleshooting
|
|
673
|
+
|
|
674
|
+
### `Broadcast::AuthenticationError` (401)
|
|
675
|
+
|
|
676
|
+
- **Wrong token:** Double-check you copied the full token from your Broadcast dashboard.
|
|
677
|
+
- **Wrong host:** If you're self-hosting, make sure `host` points to your Broadcast instance, not `sendbroadcast.com`.
|
|
678
|
+
- **Missing permissions:** Your token may not have the required permissions for the resource you're accessing. Check the [permissions table](#api-token-permissions).
|
|
679
|
+
|
|
680
|
+
### `Broadcast::ValidationError` (422)
|
|
681
|
+
|
|
682
|
+
- **Missing required fields:** Check the parameters table for the method you're calling.
|
|
683
|
+
- **Duplicate subscriber:** Creating a subscriber with an email that already exists returns 422. Use `find` first or handle the error.
|
|
684
|
+
- **Duplicate template label:** Template labels must be unique within a channel.
|
|
685
|
+
|
|
686
|
+
### `Broadcast::NotFoundError` (404)
|
|
687
|
+
|
|
688
|
+
- **Wrong ID:** The resource ID doesn't exist or belongs to a different channel.
|
|
689
|
+
- **Subscriber not found:** `subscribers.find(email: '...')` raises 404 if no subscriber matches.
|
|
690
|
+
|
|
691
|
+
### `Broadcast::TimeoutError`
|
|
692
|
+
|
|
693
|
+
- **Slow network:** Increase `timeout` and `open_timeout` in client options.
|
|
694
|
+
- **Large response:** Template list responses can be large if templates contain full HTML bodies.
|
|
695
|
+
|
|
696
|
+
### `Broadcast::RateLimitError` (429)
|
|
697
|
+
|
|
698
|
+
- **Rate limit:** Broadcast allows 120 requests per minute per token. The gem does not auto-retry on 429 -- back off and retry in your application code.
|
|
699
|
+
|
|
700
|
+
### ActionMailer emails not sending
|
|
701
|
+
|
|
702
|
+
- **Check delivery method:** Ensure `config.action_mailer.delivery_method = :broadcast` is set in the right environment file (not development or test).
|
|
703
|
+
- **Check credentials:** Run `bin/rails credentials:show` and verify `broadcast.api_token` is set.
|
|
704
|
+
- **Check logs:** Set `debug: true` in `broadcast_settings` to see request/response details.
|
|
519
705
|
|
|
520
706
|
## License
|
|
521
707
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Broadcast
|
|
4
|
+
class DeliveryMethod
|
|
5
|
+
def initialize(settings = {})
|
|
6
|
+
@client = Client.new(**settings)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def deliver!(mail)
|
|
10
|
+
@client.send_email(
|
|
11
|
+
to: mail.to&.first,
|
|
12
|
+
subject: mail.subject,
|
|
13
|
+
body: extract_body(mail),
|
|
14
|
+
reply_to: mail.reply_to&.first
|
|
15
|
+
)
|
|
16
|
+
rescue Broadcast::Error => e
|
|
17
|
+
raise DeliveryError, "Failed to deliver email: #{e.message}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def extract_body(mail)
|
|
23
|
+
if mail.html_part
|
|
24
|
+
mail.html_part.body.to_s
|
|
25
|
+
elsif mail.text_part
|
|
26
|
+
mail.text_part.body.to_s
|
|
27
|
+
else
|
|
28
|
+
mail.body.to_s
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/broadcast/errors.rb
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'delivery_method'
|
|
4
|
+
|
|
5
|
+
module Broadcast
|
|
6
|
+
class Railtie < Rails::Railtie
|
|
7
|
+
initializer 'broadcast.add_delivery_method' do
|
|
8
|
+
ActiveSupport.on_load(:action_mailer) do
|
|
9
|
+
ActionMailer::Base.add_delivery_method :broadcast, Broadcast::DeliveryMethod
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/broadcast/version.rb
CHANGED
data/lib/broadcast.rb
CHANGED
|
@@ -13,5 +13,11 @@ require_relative 'broadcast/resources/segments'
|
|
|
13
13
|
require_relative 'broadcast/resources/templates'
|
|
14
14
|
require_relative 'broadcast/resources/webhook_endpoints'
|
|
15
15
|
|
|
16
|
+
# ActionMailer integration — only loaded when Rails is present
|
|
17
|
+
if defined?(Rails::Railtie)
|
|
18
|
+
require_relative 'broadcast/delivery_method'
|
|
19
|
+
require_relative 'broadcast/railtie'
|
|
20
|
+
end
|
|
21
|
+
|
|
16
22
|
module Broadcast
|
|
17
23
|
end
|
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.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Simon Chiu
|
|
@@ -40,7 +40,9 @@ files:
|
|
|
40
40
|
- lib/broadcast.rb
|
|
41
41
|
- lib/broadcast/client.rb
|
|
42
42
|
- lib/broadcast/configuration.rb
|
|
43
|
+
- lib/broadcast/delivery_method.rb
|
|
43
44
|
- lib/broadcast/errors.rb
|
|
45
|
+
- lib/broadcast/railtie.rb
|
|
44
46
|
- lib/broadcast/resources/base.rb
|
|
45
47
|
- lib/broadcast/resources/broadcasts.rb
|
|
46
48
|
- lib/broadcast/resources/segments.rb
|