courrier 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 +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +16 -24
- data/README.md +178 -74
- data/courrier.gemspec +4 -4
- data/lib/courrier/configuration.rb +2 -4
- data/lib/courrier/email/provider.rb +14 -6
- data/lib/courrier/email/providers/base.rb +7 -2
- data/lib/courrier/email/providers/cloudflare.rb +35 -0
- data/lib/courrier/email/providers/lettermint.rb +31 -0
- data/lib/courrier/email/providers/loops.rb +1 -1
- data/lib/courrier/email/providers/mailgun.rb +1 -1
- data/lib/courrier/email/providers/mailjet.rb +1 -1
- data/lib/courrier/email/providers/mailpace.rb +1 -1
- data/lib/courrier/email/providers/postmark.rb +1 -1
- data/lib/courrier/email/providers/resend.rb +1 -1
- data/lib/courrier/email/providers/sendgrid.rb +1 -1
- data/lib/courrier/email/providers/ses.rb +75 -0
- data/lib/courrier/email/providers/smtp2go.rb +29 -0
- data/lib/courrier/email/providers/sparkpost.rb +1 -1
- data/lib/courrier/email/providers/userlist.rb +1 -1
- data/lib/courrier/email/request.rb +1 -1
- data/lib/courrier/email/transformer.rb +9 -9
- data/lib/courrier/email.rb +50 -38
- data/lib/courrier/errors.rb +0 -2
- data/lib/courrier/markdown.rb +52 -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 +18 -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 -15
- 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: d793cbc73ad9612cbb5e010e6aed85f9885e41fc8b672d16f82a6d155f63b726
|
|
4
|
+
data.tar.gz: 1143fc17abc330c3f94192ec95aea7618583266491f109b62aee3178826ede97
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 64991e00fdb46c81164c29ffc7dc9de6078bcbc6cad12407355424f19f54e3a767e8732abf1438105d1f14b3ebe8ecf8367aa88bd7e53fadd7787fa8dd163609
|
|
7
|
+
data.tar.gz: eed7664f6a7211fc98e8cd53e0dc24ad1784e5373575c006feb04fb7d3a2706da3127c0db801b3c960697f774cd3df68df3622b975f365ed5fdfa211327aa3d3
|
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 (0.11.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!"
|
|
@@ -74,8 +76,6 @@ end
|
|
|
74
76
|
# OrderEmail.deliver to: "recipient@railsdesigner.com"
|
|
75
77
|
```
|
|
76
78
|
|
|
77
|
-
💡 Write your email content using the [Minimal Email Editor](https://railsdesigner.com/minimal-email-editor/).
|
|
78
|
-
|
|
79
79
|
|
|
80
80
|
## Configuration
|
|
81
81
|
|
|
@@ -93,8 +93,13 @@ Courrier.configure do |config|
|
|
|
93
93
|
config.default_url_options = { host: "railsdesigner.com" }
|
|
94
94
|
|
|
95
95
|
# Provider-specific configuration
|
|
96
|
+
config.providers.cloudflare.account_id = "your-account-id"
|
|
96
97
|
config.providers.loops.transactional_id = "default-template"
|
|
97
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"
|
|
98
103
|
end
|
|
99
104
|
```
|
|
100
105
|
|
|
@@ -119,12 +124,30 @@ OrderEmail.deliver to: "recipient@railsdesigner.com",\
|
|
|
119
124
|
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.
|
|
120
125
|
|
|
121
126
|
|
|
127
|
+
## Custom headers
|
|
128
|
+
|
|
129
|
+
Email classes can define custom HTTP headers that are sent with every email:
|
|
130
|
+
```ruby
|
|
131
|
+
class OrderEmail < Courrier::Email
|
|
132
|
+
headers list_unsubscribe_post: "List-Unsubscribe=One-Click"
|
|
133
|
+
|
|
134
|
+
def subject = "Rails Icons now supports SVG sprites!"
|
|
135
|
+
|
|
136
|
+
def text = # …
|
|
137
|
+
|
|
138
|
+
def html = # …
|
|
139
|
+
end
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Useful for adding provider-specific headers like List-Unsubscribe for Postmark, X-Mailer identifiers or custom metadata headers required.
|
|
143
|
+
|
|
144
|
+
|
|
122
145
|
## Custom attributes
|
|
123
146
|
|
|
124
147
|
Besides the standard email attributes (`from`, `to`, `reply_to`, etc.), you can pass any additional attributes that will be available in your email templates:
|
|
125
148
|
```ruby
|
|
126
149
|
OrderEmail.deliver to: "recipient@railsdesigner.com",\
|
|
127
|
-
download_url:
|
|
150
|
+
download_url: "https://example.com/download?token=abc123"
|
|
128
151
|
```
|
|
129
152
|
|
|
130
153
|
These custom attributes are accessible directly in your email class:
|
|
@@ -155,7 +178,7 @@ When sending an email through Courrier, a `Result` object is returned that provi
|
|
|
155
178
|
### Example
|
|
156
179
|
|
|
157
180
|
```ruby
|
|
158
|
-
delivery = OrderEmail.deliver
|
|
181
|
+
delivery = OrderEmail.deliver to: "recipient@example.com"
|
|
159
182
|
|
|
160
183
|
if delivery.success?
|
|
161
184
|
puts "Email sent successfully!"
|
|
@@ -170,11 +193,17 @@ end
|
|
|
170
193
|
|
|
171
194
|
Courrier supports these transactional email providers:
|
|
172
195
|
|
|
196
|
+
- [AWS SES](https://aws.amazon.com/ses/) — requires `aws-sigv4` gem
|
|
197
|
+
- [Cloudflare Email Service](https://developers.cloudflare.com/email-service/)
|
|
198
|
+
- [Lettermint](https://lettermint.co)
|
|
173
199
|
- [Loops](https://loops.so)
|
|
174
200
|
- [Mailgun](https://mailgun.com)
|
|
175
201
|
- [MailPace](https://mailpace.com)
|
|
176
202
|
- [Postmark](https://postmarkapp.com)
|
|
177
203
|
- [Resend](https://resend.com)
|
|
204
|
+
- [SendGrid](https://sendgrid.com)
|
|
205
|
+
- [SMTP2GO](https://www.smtp2go.com/)
|
|
206
|
+
- [SparkPost](https://www.sparkpost.com/)
|
|
178
207
|
- [Userlist](https://userlist.com)
|
|
179
208
|
|
|
180
209
|
|
|
@@ -183,36 +212,6 @@ Courrier supports these transactional email providers:
|
|
|
183
212
|
Additional functionality to help with development and testing:
|
|
184
213
|
|
|
185
214
|
|
|
186
|
-
### Background jobs (Rails only)
|
|
187
|
-
|
|
188
|
-
Use `deliver_later` to enqueue delivering using Rails' ActiveJob. You can set
|
|
189
|
-
various ActiveJob-supported options in the email class, like so: `enqueue queue: "emails", wait: 5.minutes`.
|
|
190
|
-
|
|
191
|
-
- `queue`, enqueue the email on the specified queue;
|
|
192
|
-
- `wait`, enqueue the email to be delivered with a delay;
|
|
193
|
-
- `wait_until`, enqueue the email to be delivered at (after) a specific date/time;
|
|
194
|
-
- `priority`, enqueues the email with the specified priority.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
### Inbox (Rails only)
|
|
198
|
-
|
|
199
|
-
You can preview your emails in the inbox:
|
|
200
|
-
```ruby
|
|
201
|
-
config.provider = "inbox"
|
|
202
|
-
|
|
203
|
-
# And add to your routes:
|
|
204
|
-
mount Courrier::Engine => "/courrier"
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
If you want to automatically open every email in your default browser:
|
|
208
|
-
```ruby
|
|
209
|
-
config.provider = "inbox"
|
|
210
|
-
config.inbox.auto_open = true
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
Emails are automatically cleared with `bin/rails tmp:clear`, or manually with `bin/rails courrier:clear`.
|
|
214
|
-
|
|
215
|
-
|
|
216
215
|
### Layout support
|
|
217
216
|
|
|
218
217
|
Wrap your email content using layouts:
|
|
@@ -262,17 +261,18 @@ Instead of defining `text` and `html` methods, you can create ERB template files
|
|
|
262
261
|
```ruby
|
|
263
262
|
class OrderEmail < Courrier::Email
|
|
264
263
|
def subject = "Your order is ready!"
|
|
264
|
+
|
|
265
265
|
# text and html content will be loaded from template files
|
|
266
266
|
end
|
|
267
267
|
```
|
|
268
268
|
|
|
269
|
-
Create template files alongside your email class:
|
|
270
|
-
- `
|
|
271
|
-
- `
|
|
269
|
+
Create template files alongside your email class (default path is `courrier/emails`):
|
|
270
|
+
- `courrier/emails/order_email.text.erb`
|
|
271
|
+
- `courrier/emails/order_email.html.erb`
|
|
272
272
|
|
|
273
273
|
Templates have access to all context options and instance variables:
|
|
274
274
|
```erb
|
|
275
|
-
<!--
|
|
275
|
+
<!-- courrier/emails/order_email.html.erb -->
|
|
276
276
|
<h1>Hello <%= name %>!</h1>
|
|
277
277
|
<p>Your order #<%= order_id %> is ready for pickup.</p>
|
|
278
278
|
```
|
|
@@ -284,33 +284,69 @@ class OrderEmail < Courrier::Email
|
|
|
284
284
|
|
|
285
285
|
def text = "Hello #{name}! Your order ##{order_id} is ready."
|
|
286
286
|
|
|
287
|
-
# html will be loaded from
|
|
287
|
+
# html will be loaded from courrier/emails/order_email.html.erb
|
|
288
288
|
end
|
|
289
289
|
```
|
|
290
290
|
|
|
291
291
|
|
|
292
|
-
|
|
292
|
+
## Markdown support
|
|
293
293
|
|
|
294
|
-
|
|
295
|
-
```ruby
|
|
296
|
-
config.auto_generate_text = true # Defaults to false
|
|
297
|
-
```
|
|
294
|
+
Courrier supports rendering markdown content to HTML when a markdown gem is available. Simply bundle any supported markdown gem (`redcarpet`, `kramdown` or `commonmarker`) and it will be used.
|
|
298
295
|
|
|
299
296
|
|
|
300
|
-
###
|
|
297
|
+
### Markdown methods
|
|
301
298
|
|
|
302
|
-
|
|
299
|
+
Define a `markdown` method in your email class:
|
|
303
300
|
```ruby
|
|
304
|
-
class
|
|
305
|
-
def
|
|
306
|
-
recipient = email_with_name("devs@railsdesigner.com", "Rails Designer Devs")
|
|
301
|
+
class OrderEmail < Courrier::Email
|
|
302
|
+
def subject = "Your order is ready!"
|
|
307
303
|
|
|
308
|
-
|
|
304
|
+
def markdown
|
|
305
|
+
<<~MARKDOWN
|
|
306
|
+
# Hello #{name}!
|
|
307
|
+
|
|
308
|
+
Your order **##{order_id}** is ready for pickup.
|
|
309
|
+
|
|
310
|
+
## Order Details
|
|
311
|
+
- Item: #{item_name}
|
|
312
|
+
- Price: #{price}
|
|
313
|
+
MARKDOWN
|
|
309
314
|
end
|
|
310
315
|
end
|
|
311
316
|
```
|
|
312
317
|
|
|
313
|
-
|
|
318
|
+
|
|
319
|
+
### Markdown templates
|
|
320
|
+
|
|
321
|
+
Create markdown template files alongside your email class (default path is `courrier/emails`):
|
|
322
|
+
- `courrier/emails/order_email.md.erb`
|
|
323
|
+
- `courrier/emails/order_email.markdown.erb`
|
|
324
|
+
|
|
325
|
+
```erb
|
|
326
|
+
<!-- courrier/emails/order_email.md.erb -->
|
|
327
|
+
# Hello <%= name %>!
|
|
328
|
+
|
|
329
|
+
Your order **#<%= order_id %>** is ready for pickup.
|
|
330
|
+
|
|
331
|
+
## Order Details
|
|
332
|
+
- Item: <%= item_name %>
|
|
333
|
+
- Price: <%= price %>
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
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.
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
### Auto-generate text from HTML
|
|
340
|
+
|
|
341
|
+
Automatically generate plain text versions from your HTML emails:
|
|
342
|
+
```ruby
|
|
343
|
+
config.auto_generate_text = true # defaults to false
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
### Email address helper
|
|
348
|
+
|
|
349
|
+
Compose email addresses with display names:
|
|
314
350
|
```ruby
|
|
315
351
|
class Signup
|
|
316
352
|
include Courrier::Email::Address
|
|
@@ -329,8 +365,8 @@ end
|
|
|
329
365
|
Use Ruby's built-in Logger for development and testing:
|
|
330
366
|
|
|
331
367
|
```ruby
|
|
332
|
-
config.provider = "logger"
|
|
333
|
-
config.logger = custom_logger
|
|
368
|
+
config.provider = "logger" # outputs emails to STDOUT
|
|
369
|
+
config.logger = custom_logger # optional: defaults to ::Logger.new($stdout)
|
|
334
370
|
```
|
|
335
371
|
|
|
336
372
|
|
|
@@ -355,6 +391,82 @@ config.provider = "CustomProvider"
|
|
|
355
391
|
Check the [existing providers](https://github.com/Rails-Designer/courrier/tree/main/lib/courrier/email/providers) for implementation examples.
|
|
356
392
|
|
|
357
393
|
|
|
394
|
+
### Testing
|
|
395
|
+
|
|
396
|
+
Courrier provides `Test` and `TestHelper` for testing email delivery, similar to Action Mailer's testing API.
|
|
397
|
+
|
|
398
|
+
Access all delivered emails:
|
|
399
|
+
```ruby
|
|
400
|
+
# Clear deliveries between tests
|
|
401
|
+
Courrier::Test.clear!
|
|
402
|
+
|
|
403
|
+
# Access all deliveries
|
|
404
|
+
Courrier::Test.deliveries
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Each delivery record contains:
|
|
408
|
+
|
|
409
|
+
- `email_class`; the email class name
|
|
410
|
+
- `to`, `from`, `reply_to`, `cc`, `bcc`; email addresses
|
|
411
|
+
- `subject`; email subject
|
|
412
|
+
- `body` - Hash with `:text` and `:html` keys
|
|
413
|
+
- `headers`; custom headers
|
|
414
|
+
- `provider`; provider used
|
|
415
|
+
- `result`; result object with `success?` method
|
|
416
|
+
- `timestamp`; delivery time
|
|
417
|
+
|
|
418
|
+
Include the helper in your test class for assertions:
|
|
419
|
+
```ruby
|
|
420
|
+
class OrderTest < Minitest::Test
|
|
421
|
+
include Courrier::TestHelper
|
|
422
|
+
|
|
423
|
+
def setup
|
|
424
|
+
Courrier::Test.clear!
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def test_sends_confirmation_email
|
|
428
|
+
order = Order.create! product: "Widget", customer_email: "customer@example.com"
|
|
429
|
+
|
|
430
|
+
OrderEmail.deliver to: order.customer_email, order: order
|
|
431
|
+
|
|
432
|
+
assert_emails_delivered 1
|
|
433
|
+
assert_email_delivered to: "customer@example.com"
|
|
434
|
+
assert_email_delivered OrderEmail, subject: "Order"
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def test_no_emails_when_order_cancelled
|
|
438
|
+
order = Order.create! product: "Widget", status: :cancelled
|
|
439
|
+
|
|
440
|
+
assert_no_emails_delivered
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Available assertions:
|
|
446
|
+
|
|
447
|
+
- `assert_emails_delivered(count)`; assert the number of emails delivered
|
|
448
|
+
- `assert_no_emails_delivered`; assert no emails were delivered
|
|
449
|
+
- `assert_email_delivered(email_class, to:, from:, subject:, provider:)`; assert an email matching criteria was delivered
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
### Delivery callbacks
|
|
453
|
+
|
|
454
|
+
Hook into the email delivery lifecycle with `before_deliver` and `after_deliver` callbacks:
|
|
455
|
+
```ruby
|
|
456
|
+
class OrderEmail < Courrier::Email
|
|
457
|
+
before_deliver do |email|
|
|
458
|
+
puts "Sending to #{email.options.to}" # access email options, abort delivery by returning false
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
after_deliver do |email, result|
|
|
462
|
+
puts "Delivered: #{result.success?}" # access email and delivery result
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Callbacks are isolated per class (subclasses don't inherit parent callbacks).
|
|
468
|
+
|
|
469
|
+
|
|
358
470
|
## Newsletter subscriptions
|
|
359
471
|
|
|
360
472
|
Manage subscribers across popular email marketing platforms:
|
|
@@ -401,6 +513,7 @@ config.subscriber = {
|
|
|
401
513
|
}
|
|
402
514
|
```
|
|
403
515
|
|
|
516
|
+
|
|
404
517
|
### Custom providers
|
|
405
518
|
|
|
406
519
|
Create custom providers by inheriting from `Courrier::Subscriber::Base`:
|
|
@@ -440,20 +553,11 @@ See [existing providers](https://github.com/Rails-Designer/courrier/tree/main/li
|
|
|
440
553
|
|
|
441
554
|
## FAQ
|
|
442
555
|
|
|
443
|
-
### Is this a replacement for ActionMailer?
|
|
444
|
-
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.
|
|
445
|
-
|
|
446
556
|
### Is this for Rails only?
|
|
447
|
-
Not at all!
|
|
557
|
+
Not at all! Courrier works with any Ruby application. For Rails apps, use [`rails_courrier`](https://github.com/Rails-Designer/rails_courrier).
|
|
448
558
|
|
|
449
559
|
### Can it send using SMTP?
|
|
450
|
-
No
|
|
451
|
-
|
|
452
|
-
### Can separate view templates be created (like ActionMailer)?
|
|
453
|
-
The approach is different here. Instead of separate view files, email content is defined right in the email class using `text` and `html` methods. Layouts can be used to share common templates. This makes emails more self-contained and easier to reason about.
|
|
454
|
-
|
|
455
|
-
### What's the main benefit over ActionMailer?
|
|
456
|
-
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).
|
|
560
|
+
No. Courrier is specifically built for API-based email delivery.
|
|
457
561
|
|
|
458
562
|
|
|
459
563
|
## 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 = ">=
|
|
21
|
+
spec.required_ruby_version = ">= 4.0.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,7 +40,6 @@ module Courrier
|
|
|
41
40
|
@bcc = nil
|
|
42
41
|
|
|
43
42
|
@providers = Courrier::Configuration::Providers.new
|
|
44
|
-
@inbox = Courrier::Configuration::Inbox.new
|
|
45
43
|
end
|
|
46
44
|
|
|
47
45
|
def provider
|
|
@@ -71,7 +69,7 @@ module Courrier
|
|
|
71
69
|
private
|
|
72
70
|
|
|
73
71
|
def default_email_path
|
|
74
|
-
|
|
72
|
+
File.join("courrier", "emails")
|
|
75
73
|
end
|
|
76
74
|
end
|
|
77
75
|
end
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "courrier/email/providers/base"
|
|
4
|
-
require "courrier/email/providers/
|
|
4
|
+
require "courrier/email/providers/cloudflare"
|
|
5
|
+
require "courrier/email/providers/lettermint"
|
|
5
6
|
require "courrier/email/providers/logger"
|
|
6
7
|
require "courrier/email/providers/loops"
|
|
7
8
|
require "courrier/email/providers/mailgun"
|
|
@@ -9,7 +10,9 @@ require "courrier/email/providers/mailjet"
|
|
|
9
10
|
require "courrier/email/providers/mailpace"
|
|
10
11
|
require "courrier/email/providers/postmark"
|
|
11
12
|
require "courrier/email/providers/resend"
|
|
13
|
+
require "courrier/email/providers/ses"
|
|
12
14
|
require "courrier/email/providers/sendgrid"
|
|
15
|
+
require "courrier/email/providers/smtp2go"
|
|
13
16
|
require "courrier/email/providers/sparkpost"
|
|
14
17
|
require "courrier/email/providers/userlist"
|
|
15
18
|
|
|
@@ -17,26 +20,30 @@ module Courrier
|
|
|
17
20
|
class Email
|
|
18
21
|
class Provider
|
|
19
22
|
PROVIDERS = {
|
|
20
|
-
|
|
23
|
+
cloudflare: Courrier::Email::Providers::Cloudflare,
|
|
21
24
|
logger: Courrier::Email::Providers::Logger,
|
|
25
|
+
lettermint: Courrier::Email::Providers::Lettermint,
|
|
22
26
|
loops: Courrier::Email::Providers::Loops,
|
|
23
27
|
mailgun: Courrier::Email::Providers::Mailgun,
|
|
24
28
|
mailjet: Courrier::Email::Providers::Mailjet,
|
|
25
29
|
mailpace: Courrier::Email::Providers::Mailpace,
|
|
26
30
|
postmark: Courrier::Email::Providers::Postmark,
|
|
27
31
|
resend: Courrier::Email::Providers::Resend,
|
|
32
|
+
ses: Courrier::Email::Providers::Ses,
|
|
28
33
|
sendgrid: Courrier::Email::Providers::Sendgrid,
|
|
34
|
+
smtp2go: Courrier::Email::Providers::Smtp2go,
|
|
29
35
|
sparkpost: Courrier::Email::Providers::Sparkpost,
|
|
30
36
|
userlist: Courrier::Email::Providers::Userlist
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
def initialize(provider: nil, api_key: nil, options: {}, provider_options: {}, context_options: {})
|
|
39
|
+
def initialize(provider: nil, api_key: nil, options: {}, provider_options: {}, context_options: {}, custom_headers: {})
|
|
34
40
|
@provider = provider
|
|
35
41
|
@api_key = api_key
|
|
36
42
|
|
|
37
43
|
@options = options
|
|
38
44
|
@provider_options = provider_options
|
|
39
45
|
@context_options = context_options
|
|
46
|
+
@custom_headers = custom_headers
|
|
40
47
|
end
|
|
41
48
|
|
|
42
49
|
def deliver
|
|
@@ -47,7 +54,8 @@ module Courrier
|
|
|
47
54
|
api_key: @api_key,
|
|
48
55
|
options: @options,
|
|
49
56
|
provider_options: @provider_options,
|
|
50
|
-
context_options: @context_options
|
|
57
|
+
context_options: @context_options,
|
|
58
|
+
custom_headers: @custom_headers
|
|
51
59
|
).deliver
|
|
52
60
|
end
|
|
53
61
|
|
|
@@ -70,7 +78,7 @@ module Courrier
|
|
|
70
78
|
end
|
|
71
79
|
|
|
72
80
|
def api_key_required_providers?
|
|
73
|
-
!%w[logger
|
|
81
|
+
!%w[logger].include?(@provider.to_s)
|
|
74
82
|
end
|
|
75
83
|
|
|
76
84
|
def api_key_blank?
|
|
@@ -78,7 +86,7 @@ module Courrier
|
|
|
78
86
|
end
|
|
79
87
|
|
|
80
88
|
def production?
|
|
81
|
-
|
|
89
|
+
ENV["RACK_ENV"] == "production"
|
|
82
90
|
end
|
|
83
91
|
end
|
|
84
92
|
end
|
|
@@ -6,11 +6,12 @@ module Courrier
|
|
|
6
6
|
class Email
|
|
7
7
|
module Providers
|
|
8
8
|
class Base
|
|
9
|
-
def initialize(api_key: nil, options: {}, provider_options: {}, context_options: {})
|
|
9
|
+
def initialize(api_key: nil, options: {}, provider_options: {}, context_options: {}, custom_headers: {})
|
|
10
10
|
@api_key = api_key
|
|
11
11
|
@options = options
|
|
12
12
|
@provider_options = provider_options
|
|
13
13
|
@context_options = context_options
|
|
14
|
+
@custom_headers = custom_headers
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def deliver
|
|
@@ -31,7 +32,11 @@ module Courrier
|
|
|
31
32
|
|
|
32
33
|
def content_type = "application/json"
|
|
33
34
|
|
|
34
|
-
def headers
|
|
35
|
+
def headers
|
|
36
|
+
default_headers.merge(@custom_headers)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def default_headers = {}
|
|
35
40
|
|
|
36
41
|
def provider = self.class.name.split("::").last
|
|
37
42
|
end
|