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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Gemfile.lock +16 -24
  4. data/README.md +173 -121
  5. data/courrier.gemspec +4 -4
  6. data/lib/courrier/configuration.rb +2 -28
  7. data/lib/courrier/email/provider.rb +12 -4
  8. data/lib/courrier/email/providers/base.rb +2 -0
  9. data/lib/courrier/email/providers/brevo.rb +36 -0
  10. data/lib/courrier/email/providers/cloudflare.rb +37 -0
  11. data/lib/courrier/email/providers/lettermint.rb +33 -0
  12. data/lib/courrier/email/providers/loops.rb +2 -0
  13. data/lib/courrier/email/providers/mailgun.rb +2 -0
  14. data/lib/courrier/email/providers/mailjet.rb +2 -0
  15. data/lib/courrier/email/providers/postmark.rb +2 -0
  16. data/lib/courrier/email/providers/ses.rb +77 -0
  17. data/lib/courrier/email/providers/smtp2go.rb +29 -0
  18. data/lib/courrier/email/providers/userlist.rb +2 -0
  19. data/lib/courrier/email/request.rb +1 -1
  20. data/lib/courrier/email/transformer.rb +9 -9
  21. data/lib/courrier/email.rb +30 -37
  22. data/lib/courrier/errors.rb +0 -2
  23. data/lib/courrier/subscriber/brevo.rb +39 -0
  24. data/lib/courrier/test.rb +38 -0
  25. data/lib/courrier/test_helper.rb +65 -0
  26. data/lib/courrier/version.rb +1 -1
  27. data/lib/courrier.rb +2 -2
  28. metadata +19 -31
  29. data/app/controllers/courrier/previews/cleanups_controller.rb +0 -13
  30. data/app/controllers/courrier/previews_controller.rb +0 -23
  31. data/app/views/courrier/previews/index.html.erb +0 -171
  32. data/config/routes.rb +0 -8
  33. data/lib/courrier/configuration/inbox.rb +0 -21
  34. data/lib/courrier/email/providers/inbox/default.html.erb +0 -126
  35. data/lib/courrier/email/providers/inbox.rb +0 -83
  36. data/lib/courrier/engine.rb +0 -7
  37. data/lib/courrier/jobs/email_delivery_job.rb +0 -23
  38. data/lib/courrier/railtie.rb +0 -23
  39. data/lib/courrier/tasks/courrier.rake +0 -13
  40. data/lib/generators/courrier/email_generator.rb +0 -42
  41. data/lib/generators/courrier/install_generator.rb +0 -11
  42. data/lib/generators/courrier/templates/email/password_reset.rb.tt +0 -29
  43. data/lib/generators/courrier/templates/email/welcome.rb.tt +0 -43
  44. data/lib/generators/courrier/templates/email.rb.tt +0 -13
  45. 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: 555584763bab84eb75f07a5a7fa43fb4e4a65c3b1bb52de2c8e551c744fd0625
4
- data.tar.gz: 4901dfab5c8c70d2f741e6c5edc0014e2aa7a9ca4afddc011d0d946464da709d
3
+ metadata.gz: b0a97b8804b294b065433077d025cde44716b87821214649a6421cac278beca5
4
+ data.tar.gz: 814110bf00d835bfacb15bc8f59bdeb04a0cb7f7b572d4418c3fd901e98c1115
5
5
  SHA512:
6
- metadata.gz: 93d5a592840d197493eafc3b8cac47b03218b9d8bdce88ef2e666685468a32879583ea7bb7adc1e84a665864ee5d4aebc80e982606f78836d2cf996e7fd5e602
7
- data.tar.gz: d2e4391d1ea6d24031fbba9539f4935386b552184e145a4a2582e0a9a72003d3f79ed2afc6091ca9b2f4069b725e8a778602b8d31fe688999fad2badc4535276
6
+ metadata.gz: fc4503ec4cef48dbd12f67f093ccb3df9b29834d34b26f99af90b8020f9be2f7207b7629be79d50ba90af9b7b01c47e104568c1813b7b5296211ed788f453831
7
+ data.tar.gz: d50436d4e7417f481f3f2ad2cc59962b30f42fd60d471f7226128e02e04c0378c117968a8c64c8346a735af2e1dae843d1d0447bdb9ce91153850d9e67af1d83
data/Gemfile CHANGED
@@ -5,7 +5,7 @@ source "https://rubygems.org"
5
5
  gemspec
6
6
 
7
7
  group :development do
8
- gem "standard", "~> 1.49"
8
+ gem "standard", "~> 1.54.0"
9
9
  end
10
10
 
11
11
  group :development, :test do
data/Gemfile.lock CHANGED
@@ -1,18 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- courrier (0.10.0)
5
- launchy (>= 3.1, < 4)
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.25.5)
34
- nokogiri (1.18.8-arm64-darwin)
25
+ minitest (5.27.0)
26
+ nokogiri (1.19.3-arm64-darwin)
35
27
  racc (~> 1.4)
36
- nokogiri (1.18.8-x86_64-darwin)
28
+ nokogiri (1.19.3-x86_64-darwin)
37
29
  racc (~> 1.4)
38
- nokogiri (1.18.8-x86_64-linux-gnu)
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.4.0)
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.75.3)
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.44.0, < 2.0)
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.44.1)
62
+ rubocop-ast (1.49.1)
72
63
  parser (>= 3.3.7.2)
73
- prism (~> 1.4)
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.49.0)
70
+ standard (1.54.0)
80
71
  language_server-protocol (~> 3.17.0.2)
81
72
  lint_roller (~> 1.0)
82
- rubocop (~> 1.75.2)
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.4)
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.49)
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
- Generate the configuration file:
43
- ```bash
44
- bin/rails generate courrier:install
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
- This creates `config/initializers/courrier.rb` for configuring email providers and default settings.
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 HTTP headers that are sent with every email:
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
- def markdown = # …
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, or custom metadata headers required.
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: downloads_path(token: "token")
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(to: "recipient@example.com")
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 help with development and testing:
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
- - `app/emails/order_email.text.erb`
286
- - `app/emails/order_email.html.erb`
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
- <!-- app/emails/order_email.html.erb -->
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 app/emails/order_email.html.erb
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 content to HTML when a markdown gem is available. Simply bundle any supported markdown gem (`redcarpet`, `kramdown` or `commonmarker`) and it will be used.
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
- - `app/emails/order_email.md.erb`
338
- - `app/emails/order_email.markdown.erb`
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
- <!-- app/emails/order_email.md.erb -->
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 # Defaults to false
343
+ config.auto_generate_text = true # defaults to false
359
344
  ```
360
345
 
361
346
 
362
- ### Email address helper
347
+ ### Delivery callbacks
363
348
 
364
- Compose email addresses with display names:
349
+ Hook into the email delivery lifecycle with `before_deliver` and `after_deliver` callbacks:
365
350
  ```ruby
366
- class SignupsController < ApplicationController
367
- def create
368
- recipient = email_with_name("devs@railsdesigner.com", "Rails Designer Devs")
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
- WelcomeEmail.deliver to: recipient
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
- In Plain Ruby Objects:
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.provider = "logger" # outputs emails to STDOUT
395
- config.logger = custom_logger # optional: defaults to ::Logger.new($stdout)
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.provider = "CustomProvider"
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
- Manage subscribers across popular email marketing platforms:
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! While Courrier has some Rails-specific goodies (like the inbox preview feature and generators), it works great with any Ruby application.
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. If SMTP is needed, ActionMailer would be a better choices.
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,app,config,lib}/**/*", "Rakefile", "README.md", "courrier.gemspec", "Gemfile", "Gemfile.lock"]
19
+ spec.files = Dir["{bin,lib}/**/*", "Rakefile", "README.md", "courrier.gemspec", "Gemfile", "Gemfile.lock"]
20
20
 
21
- spec.required_ruby_version = ">= 3.2.0"
21
+ spec.required_ruby_version = ">= 3.4.0"
22
22
 
23
- spec.add_dependency "launchy", ">= 3.1", "< 4"
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, :inbox
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
- defined?(Rails) ? Rails.root.join("app", "emails").to_s : File.join("courrier", "emails")
48
+ File.join("courrier", "emails")
75
49
  end
76
50
  end
77
51
  end