courrier 0.10.0 → 1.0.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/Gemfile +1 -1
- data/Gemfile.lock +16 -24
- data/README.md +173 -121
- data/courrier.gemspec +4 -4
- data/lib/courrier/configuration.rb +2 -28
- data/lib/courrier/email/provider.rb +12 -4
- data/lib/courrier/email/providers/base.rb +2 -0
- data/lib/courrier/email/providers/brevo.rb +36 -0
- data/lib/courrier/email/providers/cloudflare.rb +37 -0
- data/lib/courrier/email/providers/lettermint.rb +33 -0
- data/lib/courrier/email/providers/loops.rb +2 -0
- data/lib/courrier/email/providers/mailgun.rb +2 -0
- data/lib/courrier/email/providers/mailjet.rb +2 -0
- data/lib/courrier/email/providers/postmark.rb +2 -0
- data/lib/courrier/email/providers/ses.rb +77 -0
- data/lib/courrier/email/providers/smtp2go.rb +29 -0
- data/lib/courrier/email/providers/userlist.rb +2 -0
- data/lib/courrier/email/request.rb +1 -1
- data/lib/courrier/email/transformer.rb +9 -9
- data/lib/courrier/email.rb +30 -37
- data/lib/courrier/errors.rb +0 -2
- data/lib/courrier/subscriber/brevo.rb +39 -0
- data/lib/courrier/test.rb +38 -0
- data/lib/courrier/test_helper.rb +65 -0
- data/lib/courrier/version.rb +1 -1
- data/lib/courrier.rb +2 -2
- metadata +19 -31
- data/app/controllers/courrier/previews/cleanups_controller.rb +0 -13
- data/app/controllers/courrier/previews_controller.rb +0 -23
- data/app/views/courrier/previews/index.html.erb +0 -171
- data/config/routes.rb +0 -8
- data/lib/courrier/configuration/inbox.rb +0 -21
- data/lib/courrier/email/providers/inbox/default.html.erb +0 -126
- data/lib/courrier/email/providers/inbox.rb +0 -83
- data/lib/courrier/engine.rb +0 -7
- data/lib/courrier/jobs/email_delivery_job.rb +0 -23
- data/lib/courrier/railtie.rb +0 -23
- data/lib/courrier/tasks/courrier.rake +0 -13
- data/lib/generators/courrier/email_generator.rb +0 -42
- data/lib/generators/courrier/install_generator.rb +0 -11
- data/lib/generators/courrier/templates/email/password_reset.rb.tt +0 -29
- data/lib/generators/courrier/templates/email/welcome.rb.tt +0 -43
- data/lib/generators/courrier/templates/email.rb.tt +0 -13
- data/lib/generators/courrier/templates/initializer.rb.tt +0 -43
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b0a97b8804b294b065433077d025cde44716b87821214649a6421cac278beca5
|
|
4
|
+
data.tar.gz: 814110bf00d835bfacb15bc8f59bdeb04a0cb7f7b572d4418c3fd901e98c1115
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fc4503ec4cef48dbd12f67f093ccb3df9b29834d34b26f99af90b8020f9be2f7207b7629be79d50ba90af9b7b01c47e104568c1813b7b5296211ed788f453831
|
|
7
|
+
data.tar.gz: d50436d4e7417f481f3f2ad2cc59962b30f42fd60d471f7226128e02e04c0378c117968a8c64c8346a735af2e1dae843d1d0447bdb9ce91153850d9e67af1d83
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
courrier (0.
|
|
5
|
-
|
|
4
|
+
courrier (1.0.0)
|
|
5
|
+
logger (>= 1.5, < 3)
|
|
6
6
|
nokogiri (>= 1.18, < 2)
|
|
7
7
|
|
|
8
8
|
GEM
|
|
9
9
|
remote: https://rubygems.org/
|
|
10
10
|
specs:
|
|
11
|
-
addressable (2.8.7)
|
|
12
|
-
public_suffix (>= 2.0.2, < 7.0)
|
|
13
11
|
ast (2.4.3)
|
|
14
|
-
childprocess (5.1.0)
|
|
15
|
-
logger (~> 1.5)
|
|
16
12
|
date (3.4.1)
|
|
17
13
|
debug (1.10.0)
|
|
18
14
|
irb (~> 1.10)
|
|
@@ -24,18 +20,14 @@ GEM
|
|
|
24
20
|
reline (>= 0.4.2)
|
|
25
21
|
json (2.11.1)
|
|
26
22
|
language_server-protocol (3.17.0.4)
|
|
27
|
-
launchy (3.1.1)
|
|
28
|
-
addressable (~> 2.8)
|
|
29
|
-
childprocess (~> 5.0)
|
|
30
|
-
logger (~> 1.6)
|
|
31
23
|
lint_roller (1.1.0)
|
|
32
24
|
logger (1.7.0)
|
|
33
|
-
minitest (5.
|
|
34
|
-
nokogiri (1.
|
|
25
|
+
minitest (5.27.0)
|
|
26
|
+
nokogiri (1.19.3-arm64-darwin)
|
|
35
27
|
racc (~> 1.4)
|
|
36
|
-
nokogiri (1.
|
|
28
|
+
nokogiri (1.19.3-x86_64-darwin)
|
|
37
29
|
racc (~> 1.4)
|
|
38
|
-
nokogiri (1.
|
|
30
|
+
nokogiri (1.19.3-x86_64-linux-gnu)
|
|
39
31
|
racc (~> 1.4)
|
|
40
32
|
parallel (1.27.0)
|
|
41
33
|
parser (3.3.8.0)
|
|
@@ -44,11 +36,10 @@ GEM
|
|
|
44
36
|
pp (0.6.2)
|
|
45
37
|
prettyprint
|
|
46
38
|
prettyprint (0.2.0)
|
|
47
|
-
prism (1.
|
|
39
|
+
prism (1.9.0)
|
|
48
40
|
psych (5.2.3)
|
|
49
41
|
date
|
|
50
42
|
stringio
|
|
51
|
-
public_suffix (6.0.2)
|
|
52
43
|
racc (1.8.1)
|
|
53
44
|
rainbow (3.1.1)
|
|
54
45
|
rake (13.2.1)
|
|
@@ -57,7 +48,7 @@ GEM
|
|
|
57
48
|
regexp_parser (2.10.0)
|
|
58
49
|
reline (0.6.1)
|
|
59
50
|
io-console (~> 0.5)
|
|
60
|
-
rubocop (1.
|
|
51
|
+
rubocop (1.84.2)
|
|
61
52
|
json (~> 2.3)
|
|
62
53
|
language_server-protocol (~> 3.17.0.2)
|
|
63
54
|
lint_roller (~> 1.1.0)
|
|
@@ -65,21 +56,21 @@ GEM
|
|
|
65
56
|
parser (>= 3.3.0.2)
|
|
66
57
|
rainbow (>= 2.2.2, < 4.0)
|
|
67
58
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
68
|
-
rubocop-ast (>= 1.
|
|
59
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
69
60
|
ruby-progressbar (~> 1.7)
|
|
70
61
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
71
|
-
rubocop-ast (1.
|
|
62
|
+
rubocop-ast (1.49.1)
|
|
72
63
|
parser (>= 3.3.7.2)
|
|
73
|
-
prism (~> 1.
|
|
64
|
+
prism (~> 1.7)
|
|
74
65
|
rubocop-performance (1.25.0)
|
|
75
66
|
lint_roller (~> 1.1)
|
|
76
67
|
rubocop (>= 1.75.0, < 2.0)
|
|
77
68
|
rubocop-ast (>= 1.38.0, < 2.0)
|
|
78
69
|
ruby-progressbar (1.13.0)
|
|
79
|
-
standard (1.
|
|
70
|
+
standard (1.54.0)
|
|
80
71
|
language_server-protocol (~> 3.17.0.2)
|
|
81
72
|
lint_roller (~> 1.0)
|
|
82
|
-
rubocop (~> 1.
|
|
73
|
+
rubocop (~> 1.84.0)
|
|
83
74
|
standard-custom (~> 1.0.0)
|
|
84
75
|
standard-performance (~> 1.8)
|
|
85
76
|
standard-custom (1.0.2)
|
|
@@ -91,10 +82,11 @@ GEM
|
|
|
91
82
|
stringio (3.1.7)
|
|
92
83
|
unicode-display_width (3.1.4)
|
|
93
84
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
|
94
|
-
unicode-emoji (4.0
|
|
85
|
+
unicode-emoji (4.2.0)
|
|
95
86
|
|
|
96
87
|
PLATFORMS
|
|
97
88
|
arm64-darwin-23
|
|
89
|
+
arm64-darwin-24
|
|
98
90
|
x86_64-darwin-23
|
|
99
91
|
x86_64-linux
|
|
100
92
|
|
|
@@ -103,7 +95,7 @@ DEPENDENCIES
|
|
|
103
95
|
debug (~> 1.9, >= 1.9.2)
|
|
104
96
|
minitest (~> 5.25, >= 5.25.5)
|
|
105
97
|
rake (~> 13.2, >= 13.2.1)
|
|
106
|
-
standard (~> 1.
|
|
98
|
+
standard (~> 1.54.0)
|
|
107
99
|
|
|
108
100
|
BUNDLED WITH
|
|
109
101
|
2.6.8
|
data/README.md
CHANGED
|
@@ -38,22 +38,24 @@ Add the gem:
|
|
|
38
38
|
```bash
|
|
39
39
|
bundle add courrier
|
|
40
40
|
```
|
|
41
|
+
> [!tip]
|
|
42
|
+
> For **Rails** apps, use [`rails_courrier`](https://github.com/Rails-Designer/rails_courrier) instead. It includes generators, ActiveJob support (`deliver_later`), inbox previews and more.
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
Configure Courrier in your app:
|
|
45
|
+
```ruby
|
|
46
|
+
Courrier.configure do |config|
|
|
47
|
+
config.email = {
|
|
48
|
+
provider: "postmark",
|
|
49
|
+
api_key: "your-api-key"
|
|
50
|
+
}
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
config.from = "devs@example.com"
|
|
53
|
+
end
|
|
54
|
+
```
|
|
48
55
|
|
|
49
56
|
|
|
50
57
|
## Usage
|
|
51
58
|
|
|
52
|
-
Generate a new email:
|
|
53
|
-
```bash
|
|
54
|
-
bin/rails generate courrier:email Order
|
|
55
|
-
```
|
|
56
|
-
|
|
57
59
|
```ruby
|
|
58
60
|
class OrderEmail < Courrier::Email
|
|
59
61
|
def subject = "Here is your order!"
|
|
@@ -91,8 +93,13 @@ Courrier.configure do |config|
|
|
|
91
93
|
config.default_url_options = { host: "railsdesigner.com" }
|
|
92
94
|
|
|
93
95
|
# Provider-specific configuration
|
|
96
|
+
config.providers.cloudflare.account_id = "your-account-id"
|
|
94
97
|
config.providers.loops.transactional_id = "default-template"
|
|
95
98
|
config.providers.mailgun.domain = "notifications.railsdesigner.com"
|
|
99
|
+
|
|
100
|
+
config.providers.ses.region = "us-east-1"
|
|
101
|
+
config.providers.ses.access_key_id = "your-access-key-id"
|
|
102
|
+
config.providers.ses.secret_access_key = "your-secret-access-key"
|
|
96
103
|
end
|
|
97
104
|
```
|
|
98
105
|
|
|
@@ -114,12 +121,10 @@ OrderEmail.deliver to: "recipient@railsdesigner.com",\
|
|
|
114
121
|
```
|
|
115
122
|
|
|
116
123
|
|
|
117
|
-
Provider and API key settings can be overridden using environment variables (`COURRIER_PROVIDER` and `COURRIER_API_KEY`) for both global configuration and email class defaults.
|
|
118
|
-
|
|
119
124
|
|
|
120
125
|
## Custom headers
|
|
121
126
|
|
|
122
|
-
Email classes can define custom
|
|
127
|
+
Email classes can define custom headers that are sent with every email:
|
|
123
128
|
```ruby
|
|
124
129
|
class OrderEmail < Courrier::Email
|
|
125
130
|
headers list_unsubscribe_post: "List-Unsubscribe=One-Click"
|
|
@@ -127,11 +132,12 @@ class OrderEmail < Courrier::Email
|
|
|
127
132
|
def subject = "Rails Icons now supports SVG sprites!"
|
|
128
133
|
|
|
129
134
|
def text = # …
|
|
130
|
-
|
|
135
|
+
|
|
136
|
+
def html = # …
|
|
131
137
|
end
|
|
132
138
|
```
|
|
133
139
|
|
|
134
|
-
Useful for adding provider-specific headers like List-Unsubscribe for Postmark, X-Mailer identifiers
|
|
140
|
+
Useful for adding provider-specific headers like List-Unsubscribe for Postmark, X-Mailer identifiers or custom metadata headers required.
|
|
135
141
|
|
|
136
142
|
|
|
137
143
|
## Custom attributes
|
|
@@ -139,7 +145,7 @@ Useful for adding provider-specific headers like List-Unsubscribe for Postmark,
|
|
|
139
145
|
Besides the standard email attributes (`from`, `to`, `reply_to`, etc.), you can pass any additional attributes that will be available in your email templates:
|
|
140
146
|
```ruby
|
|
141
147
|
OrderEmail.deliver to: "recipient@railsdesigner.com",\
|
|
142
|
-
download_url:
|
|
148
|
+
download_url: "https://example.com/download?token=abc123"
|
|
143
149
|
```
|
|
144
150
|
|
|
145
151
|
These custom attributes are accessible directly in your email class:
|
|
@@ -170,7 +176,7 @@ When sending an email through Courrier, a `Result` object is returned that provi
|
|
|
170
176
|
### Example
|
|
171
177
|
|
|
172
178
|
```ruby
|
|
173
|
-
delivery = OrderEmail.deliver
|
|
179
|
+
delivery = OrderEmail.deliver to: "recipient@example.com"
|
|
174
180
|
|
|
175
181
|
if delivery.success?
|
|
176
182
|
puts "Email sent successfully!"
|
|
@@ -185,109 +191,45 @@ end
|
|
|
185
191
|
|
|
186
192
|
Courrier supports these transactional email providers:
|
|
187
193
|
|
|
194
|
+
- [AWS SES](https://aws.amazon.com/ses/) — requires `aws-sigv4` gem
|
|
195
|
+
- [Brevo](https://www.brevo.com/)
|
|
196
|
+
- [Cloudflare Email Service](https://developers.cloudflare.com/email-service/)
|
|
197
|
+
- [Lettermint](https://lettermint.co)
|
|
188
198
|
- [Loops](https://loops.so)
|
|
189
199
|
- [Mailgun](https://mailgun.com)
|
|
190
200
|
- [MailPace](https://mailpace.com)
|
|
191
201
|
- [Postmark](https://postmarkapp.com)
|
|
192
202
|
- [Resend](https://resend.com)
|
|
203
|
+
- [SendGrid](https://sendgrid.com)
|
|
204
|
+
- [SMTP2GO](https://www.smtp2go.com/)
|
|
205
|
+
- [SparkPost](https://www.sparkpost.com/)
|
|
193
206
|
- [Userlist](https://userlist.com)
|
|
194
207
|
|
|
195
208
|
|
|
196
209
|
## More Features
|
|
197
210
|
|
|
198
|
-
Additional functionality to
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
### Background jobs (Rails only)
|
|
202
|
-
|
|
203
|
-
Use `deliver_later` to enqueue delivering using Rails' ActiveJob. You can set
|
|
204
|
-
various ActiveJob-supported options in the email class, like so: `enqueue queue: "emails", wait: 5.minutes`.
|
|
205
|
-
|
|
206
|
-
- `queue`, enqueue the email on the specified queue;
|
|
207
|
-
- `wait`, enqueue the email to be delivered with a delay;
|
|
208
|
-
- `wait_until`, enqueue the email to be delivered at (after) a specific date/time;
|
|
209
|
-
- `priority`, enqueues the email with the specified priority.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
### Inbox (Rails only)
|
|
213
|
-
|
|
214
|
-
You can preview your emails in the inbox:
|
|
215
|
-
```ruby
|
|
216
|
-
config.provider = "inbox"
|
|
217
|
-
|
|
218
|
-
# And add to your routes:
|
|
219
|
-
mount Courrier::Engine => "/courrier"
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
If you want to automatically open every email in your default browser:
|
|
223
|
-
```ruby
|
|
224
|
-
config.provider = "inbox"
|
|
225
|
-
config.inbox.auto_open = true
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
Emails are automatically cleared with `bin/rails tmp:clear`, or manually with `bin/rails courrier:clear`.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
### Layout support
|
|
232
|
-
|
|
233
|
-
Wrap your email content using layouts:
|
|
234
|
-
```ruby
|
|
235
|
-
class OrderEmail < Courrier::Email
|
|
236
|
-
layout text: "%{content}\n\nThanks for your order!",
|
|
237
|
-
html: "<div>\n%{content}\n</div>"
|
|
238
|
-
end
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
Using a method:
|
|
242
|
-
```ruby
|
|
243
|
-
class OrderEmail < Courrier::Email
|
|
244
|
-
layout html: :html_layout
|
|
245
|
-
|
|
246
|
-
def html_layout
|
|
247
|
-
<<~HTML
|
|
248
|
-
<div style='font-family: ui-sans-serif, system-ui;'>
|
|
249
|
-
%{content}
|
|
250
|
-
</div>
|
|
251
|
-
HTML
|
|
252
|
-
end
|
|
253
|
-
end
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
Using a separate class:
|
|
257
|
-
```ruby
|
|
258
|
-
class OrderEmail < Courrier::Email
|
|
259
|
-
layout html: OrderLayout
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
class OrderLayout
|
|
263
|
-
self.call
|
|
264
|
-
<<~HTML
|
|
265
|
-
<div style='font-family: ui-sans-serif, system-ui;'>
|
|
266
|
-
%{content}
|
|
267
|
-
</div>
|
|
268
|
-
HTML
|
|
269
|
-
end
|
|
270
|
-
end
|
|
271
|
-
```
|
|
211
|
+
Additional functionality to go beyond the basics:
|
|
272
212
|
|
|
273
213
|
|
|
274
214
|
### Template files
|
|
275
215
|
|
|
276
216
|
Instead of defining `text` and `html` methods, you can create ERB template files:
|
|
277
217
|
```ruby
|
|
218
|
+
# courrier/emails/order_email.rb
|
|
278
219
|
class OrderEmail < Courrier::Email
|
|
279
220
|
def subject = "Your order is ready!"
|
|
221
|
+
|
|
280
222
|
# text and html content will be loaded from template files
|
|
281
223
|
end
|
|
282
224
|
```
|
|
283
225
|
|
|
284
|
-
Create template files alongside your email class:
|
|
285
|
-
- `
|
|
286
|
-
- `
|
|
226
|
+
Create template files alongside your email class (default path is `courrier/emails`):
|
|
227
|
+
- `courrier/emails/order_email.text.erb`
|
|
228
|
+
- `courrier/emails/order_email.html.erb`
|
|
287
229
|
|
|
288
230
|
Templates have access to all context options and instance variables:
|
|
289
231
|
```erb
|
|
290
|
-
<!--
|
|
232
|
+
<!-- courrier/emails/order_email.html.erb -->
|
|
291
233
|
<h1>Hello <%= name %>!</h1>
|
|
292
234
|
<p>Your order #<%= order_id %> is ready for pickup.</p>
|
|
293
235
|
```
|
|
@@ -299,14 +241,14 @@ class OrderEmail < Courrier::Email
|
|
|
299
241
|
|
|
300
242
|
def text = "Hello #{name}! Your order ##{order_id} is ready."
|
|
301
243
|
|
|
302
|
-
# html will be loaded from
|
|
244
|
+
# html will be loaded from courrier/emails/order_email.html.erb
|
|
303
245
|
end
|
|
304
246
|
```
|
|
305
247
|
|
|
306
248
|
|
|
307
249
|
## Markdown support
|
|
308
250
|
|
|
309
|
-
Courrier supports rendering markdown
|
|
251
|
+
Courrier supports rendering markdown to HTML. Bundle any supported markdown gem (`redcarpet`, `kramdown` or `commonmarker`) and it will be used.
|
|
310
252
|
|
|
311
253
|
|
|
312
254
|
### Markdown methods
|
|
@@ -333,12 +275,12 @@ end
|
|
|
333
275
|
|
|
334
276
|
### Markdown templates
|
|
335
277
|
|
|
336
|
-
Create markdown template files alongside your email class:
|
|
337
|
-
- `
|
|
338
|
-
- `
|
|
278
|
+
Create markdown template files alongside your email class (default path is `courrier/emails`):
|
|
279
|
+
- `courrier/emails/order_email.md.erb`
|
|
280
|
+
- `courrier/emails/order_email.markdown.erb`
|
|
339
281
|
|
|
340
282
|
```erb
|
|
341
|
-
<!--
|
|
283
|
+
<!-- courrier/emails/order_email.md.erb -->
|
|
342
284
|
# Hello <%= name %>!
|
|
343
285
|
|
|
344
286
|
Your order **#<%= order_id %>** is ready for pickup.
|
|
@@ -351,28 +293,76 @@ Your order **#<%= order_id %>** is ready for pickup.
|
|
|
351
293
|
Method definitions take precedence over template files. You can mix approaches. For example, define `text` in a method and use a markdown template for HTML content.
|
|
352
294
|
|
|
353
295
|
|
|
296
|
+
### Layout support
|
|
297
|
+
|
|
298
|
+
Wrap your email content using layouts:
|
|
299
|
+
```ruby
|
|
300
|
+
class OrderEmail < Courrier::Email
|
|
301
|
+
layout text: "%{content}\n\nThanks for your order!",
|
|
302
|
+
html: "<div>\n%{content}\n</div>"
|
|
303
|
+
end
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Using a method:
|
|
307
|
+
```ruby
|
|
308
|
+
class OrderEmail < Courrier::Email
|
|
309
|
+
layout html: :html_layout
|
|
310
|
+
|
|
311
|
+
def html_layout
|
|
312
|
+
<<~HTML
|
|
313
|
+
<div style='font-family: ui-sans-serif, system-ui;'>
|
|
314
|
+
%{content}
|
|
315
|
+
</div>
|
|
316
|
+
HTML
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Using a separate class:
|
|
322
|
+
```ruby
|
|
323
|
+
class OrderEmail < Courrier::Email
|
|
324
|
+
layout html: OrderLayout
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
class OrderLayout
|
|
328
|
+
self.call
|
|
329
|
+
<<~HTML
|
|
330
|
+
<div style='font-family: ui-sans-serif, system-ui;'>
|
|
331
|
+
%{content}
|
|
332
|
+
</div>
|
|
333
|
+
HTML
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
|
|
354
339
|
### Auto-generate text from HTML
|
|
355
340
|
|
|
356
341
|
Automatically generate plain text versions from your HTML emails:
|
|
357
342
|
```ruby
|
|
358
|
-
config.auto_generate_text = true
|
|
343
|
+
config.auto_generate_text = true # defaults to false
|
|
359
344
|
```
|
|
360
345
|
|
|
361
346
|
|
|
362
|
-
###
|
|
347
|
+
### Delivery callbacks
|
|
363
348
|
|
|
364
|
-
|
|
349
|
+
Hook into the email delivery lifecycle with `before_deliver` and `after_deliver` callbacks:
|
|
365
350
|
```ruby
|
|
366
|
-
class
|
|
367
|
-
|
|
368
|
-
|
|
351
|
+
class OrderEmail < Courrier::Email
|
|
352
|
+
before_deliver do |email|
|
|
353
|
+
puts "Sending to #{email.options.to}" # access email options, abort delivery by returning false
|
|
354
|
+
end
|
|
369
355
|
|
|
370
|
-
|
|
356
|
+
after_deliver do |email, result|
|
|
357
|
+
puts "Delivered: #{result.success?}" # access email and delivery result
|
|
371
358
|
end
|
|
372
359
|
end
|
|
373
360
|
```
|
|
374
361
|
|
|
375
|
-
|
|
362
|
+
|
|
363
|
+
### Email address helper
|
|
364
|
+
|
|
365
|
+
Compose email addresses with display names:
|
|
376
366
|
```ruby
|
|
377
367
|
class Signup
|
|
378
368
|
include Courrier::Email::Address
|
|
@@ -391,8 +381,8 @@ end
|
|
|
391
381
|
Use Ruby's built-in Logger for development and testing:
|
|
392
382
|
|
|
393
383
|
```ruby
|
|
394
|
-
config.
|
|
395
|
-
config.logger = custom_logger
|
|
384
|
+
config.email = { provider: "logger" } # outputs emails to STDOUT
|
|
385
|
+
config.logger = custom_logger # optional: defaults to ::Logger.new($stdout)
|
|
396
386
|
```
|
|
397
387
|
|
|
398
388
|
|
|
@@ -411,15 +401,73 @@ end
|
|
|
411
401
|
|
|
412
402
|
Then configure it:
|
|
413
403
|
```ruby
|
|
414
|
-
config.
|
|
404
|
+
config.email = { provider: "CustomProvider" }
|
|
415
405
|
```
|
|
416
406
|
|
|
417
407
|
Check the [existing providers](https://github.com/Rails-Designer/courrier/tree/main/lib/courrier/email/providers) for implementation examples.
|
|
418
408
|
|
|
419
409
|
|
|
410
|
+
### Testing
|
|
411
|
+
|
|
412
|
+
Courrier provides `Test` and `TestHelper` for testing email delivery, similar to Action Mailer's testing API.
|
|
413
|
+
|
|
414
|
+
Access all delivered emails:
|
|
415
|
+
```ruby
|
|
416
|
+
# Clear deliveries between tests
|
|
417
|
+
Courrier::Test.clear!
|
|
418
|
+
|
|
419
|
+
# Access all deliveries
|
|
420
|
+
Courrier::Test.deliveries
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Each delivery record contains:
|
|
424
|
+
|
|
425
|
+
- `email_class`; the email class name
|
|
426
|
+
- `to`, `from`, `reply_to`, `cc`, `bcc`; email addresses
|
|
427
|
+
- `subject`; email subject
|
|
428
|
+
- `body` - Hash with `:text` and `:html` keys
|
|
429
|
+
- `headers`; custom headers
|
|
430
|
+
- `provider`; provider used
|
|
431
|
+
- `result`; result object with `success?` method
|
|
432
|
+
- `timestamp`; delivery time
|
|
433
|
+
|
|
434
|
+
Include the helper in your test class for assertions:
|
|
435
|
+
```ruby
|
|
436
|
+
class OrderTest < Minitest::Test
|
|
437
|
+
include Courrier::TestHelper
|
|
438
|
+
|
|
439
|
+
def setup
|
|
440
|
+
Courrier::Test.clear!
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def test_sends_confirmation_email
|
|
444
|
+
order = Order.create! product: "Widget", customer_email: "customer@example.com"
|
|
445
|
+
|
|
446
|
+
OrderEmail.deliver to: order.customer_email, order: order
|
|
447
|
+
|
|
448
|
+
assert_emails_delivered 1
|
|
449
|
+
assert_email_delivered to: "customer@example.com"
|
|
450
|
+
assert_email_delivered OrderEmail, subject: "Order"
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def test_no_emails_when_order_cancelled
|
|
454
|
+
order = Order.create! product: "Widget", status: :cancelled
|
|
455
|
+
|
|
456
|
+
assert_no_emails_delivered
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Available assertions:
|
|
462
|
+
|
|
463
|
+
- `assert_emails_delivered(count)`; assert the number of emails delivered
|
|
464
|
+
- `assert_no_emails_delivered`; assert no emails were delivered
|
|
465
|
+
- `assert_email_delivered(email_class, to:, from:, subject:, provider:)`; assert an email matching criteria was delivered
|
|
466
|
+
|
|
467
|
+
|
|
420
468
|
## Newsletter subscriptions
|
|
421
469
|
|
|
422
|
-
|
|
470
|
+
Use Courrier to manage subscribers across popular email marketing platforms:
|
|
423
471
|
```ruby
|
|
424
472
|
Courrier.configure do |config|
|
|
425
473
|
config.subscriber = {
|
|
@@ -447,6 +495,7 @@ end
|
|
|
447
495
|
### Supported providers
|
|
448
496
|
|
|
449
497
|
- [Beehiiv](https://www.beehiiv.com/) - requires `publication_id`
|
|
498
|
+
- [Brevo](https://www.brevo.com/) - accepts optional `list_ids`
|
|
450
499
|
- [Buttondown](https://buttondown.com)
|
|
451
500
|
- [Kit](https://kit.com/) (formerly ConvertKit) - requires `form_id`
|
|
452
501
|
- [Loops](https://loops.so/)
|
|
@@ -463,6 +512,15 @@ config.subscriber = {
|
|
|
463
512
|
}
|
|
464
513
|
```
|
|
465
514
|
|
|
515
|
+
For Brevo, pass one or more list IDs when new contacts should be added to specific lists:
|
|
516
|
+
```ruby
|
|
517
|
+
config.subscriber = {
|
|
518
|
+
provider: "brevo",
|
|
519
|
+
api_key: "your_api_key",
|
|
520
|
+
list_ids: [12, 34]
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
466
524
|
### Custom providers
|
|
467
525
|
|
|
468
526
|
Create custom providers by inheriting from `Courrier::Subscriber::Base`:
|
|
@@ -502,17 +560,11 @@ See [existing providers](https://github.com/Rails-Designer/courrier/tree/main/li
|
|
|
502
560
|
|
|
503
561
|
## FAQ
|
|
504
562
|
|
|
505
|
-
### Is this a replacement for ActionMailer?
|
|
506
|
-
Yes! While different in approach, Courrier can fully replace ActionMailer. It's a modern alternative that focuses on API-based delivery. The main difference is in how emails are structured - Courrier uses a more straightforward, class-based approach.
|
|
507
|
-
|
|
508
563
|
### Is this for Rails only?
|
|
509
|
-
Not at all!
|
|
564
|
+
Not at all! Courrier works with any Ruby application. For Rails apps, use [`rails_courrier`](https://github.com/Rails-Designer/rails_courrier).
|
|
510
565
|
|
|
511
566
|
### Can it send using SMTP?
|
|
512
|
-
No. Courrier is specifically built for API-based email delivery.
|
|
513
|
-
|
|
514
|
-
### What's the main benefit over ActionMailer?
|
|
515
|
-
Courrier offers a simpler, more modern approach to sending emails. Each email is a standalone class, configuration is straightforward (typically just only an API key is needed) and it packs few quality-of-life features (like the inbox feature and auto-generate text version).
|
|
567
|
+
No. Courrier is specifically built for API-based email delivery.
|
|
516
568
|
|
|
517
569
|
|
|
518
570
|
## Contributing
|
data/courrier.gemspec
CHANGED
|
@@ -10,16 +10,16 @@ Gem::Specification.new do |spec|
|
|
|
10
10
|
|
|
11
11
|
spec.summary = "API-powered email delivery for Ruby apps"
|
|
12
12
|
spec.description = "API-powered email delivery for Ruby apps with support for Postmark, SendGrid, Mailgun and more."
|
|
13
|
-
spec.homepage = "https://railsdesigner.com/courrier/"
|
|
13
|
+
spec.homepage = "https://railsdesigner.com/open-source/courrier/"
|
|
14
14
|
spec.license = "MIT"
|
|
15
15
|
|
|
16
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
17
17
|
spec.metadata["source_code_uri"] = "https://github.com/Rails-Designer/courrier/"
|
|
18
18
|
|
|
19
|
-
spec.files = Dir["{bin,
|
|
19
|
+
spec.files = Dir["{bin,lib}/**/*", "Rakefile", "README.md", "courrier.gemspec", "Gemfile", "Gemfile.lock"]
|
|
20
20
|
|
|
21
|
-
spec.required_ruby_version = ">= 3.
|
|
21
|
+
spec.required_ruby_version = ">= 3.4.0"
|
|
22
22
|
|
|
23
|
-
spec.add_dependency "
|
|
23
|
+
spec.add_dependency "logger", ">= 1.5", "< 3"
|
|
24
24
|
spec.add_dependency "nokogiri", ">= 1.18", "< 2"
|
|
25
25
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "courrier/configuration/inbox"
|
|
4
3
|
require "courrier/configuration/providers"
|
|
5
4
|
|
|
6
5
|
module Courrier
|
|
@@ -22,7 +21,7 @@ module Courrier
|
|
|
22
21
|
attr_accessor :email, :subscriber, :logger, :email_path, :layouts, :default_url_options, :auto_generate_text,
|
|
23
22
|
:from, :reply_to, :cc, :bcc
|
|
24
23
|
|
|
25
|
-
attr_reader :providers
|
|
24
|
+
attr_reader :providers
|
|
26
25
|
|
|
27
26
|
def initialize
|
|
28
27
|
@email = {provider: "logger"}
|
|
@@ -41,37 +40,12 @@ module Courrier
|
|
|
41
40
|
@bcc = nil
|
|
42
41
|
|
|
43
42
|
@providers = Courrier::Configuration::Providers.new
|
|
44
|
-
@inbox = Courrier::Configuration::Inbox.new
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def provider
|
|
48
|
-
warn "[DEPRECATION] `provider` is deprecated. Use `email = { provider: '…' }` instead. Will be removed in 1.0.0"
|
|
49
|
-
|
|
50
|
-
@email[:provider]
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def provider=(value)
|
|
54
|
-
warn "[DEPRECATION] `provider=` is deprecated. Use `email = { provider: '…' }` instead. Will be removed in 1.0.0"
|
|
55
|
-
|
|
56
|
-
@email[:provider] = value
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def api_key
|
|
60
|
-
warn "[DEPRECATION] `api_key` is deprecated. Use `email = { api_key: '…' }` instead. Will be removed in 1.0.0"
|
|
61
|
-
|
|
62
|
-
@email[:api_key]
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def api_key=(value)
|
|
66
|
-
warn "[DEPRECATION] `api_key=` is deprecated. Use `email = { api_key: '…' }` instead. Will be removed in 1.0.0"
|
|
67
|
-
|
|
68
|
-
@email[:api_key] = value
|
|
69
43
|
end
|
|
70
44
|
|
|
71
45
|
private
|
|
72
46
|
|
|
73
47
|
def default_email_path
|
|
74
|
-
|
|
48
|
+
File.join("courrier", "emails")
|
|
75
49
|
end
|
|
76
50
|
end
|
|
77
51
|
end
|