courrier 0.11.0 → 1.1.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.lock +1 -1
- data/README.md +81 -69
- data/courrier.gemspec +1 -1
- data/lib/courrier/configuration.rb +0 -24
- data/lib/courrier/context_value.rb +33 -0
- data/lib/courrier/email/options.rb +3 -3
- data/lib/courrier/email/provider.rb +10 -0
- data/lib/courrier/email/providers/base.rb +19 -0
- data/lib/courrier/email/providers/brevo.rb +28 -0
- data/lib/courrier/email/providers/cloudflare.rb +2 -0
- data/lib/courrier/email/providers/lettermint.rb +2 -0
- data/lib/courrier/email/providers/loops.rb +2 -0
- data/lib/courrier/email/providers/mailersend.rb +28 -0
- data/lib/courrier/email/providers/mailgun.rb +5 -1
- data/lib/courrier/email/providers/mailjet.rb +10 -6
- data/lib/courrier/email/providers/mailkite.rb +32 -0
- data/lib/courrier/email/providers/mailpace.rb +2 -0
- data/lib/courrier/email/providers/mailtrap.rb +28 -0
- data/lib/courrier/email/providers/postmark.rb +4 -0
- data/lib/courrier/email/providers/sendgrid.rb +4 -6
- data/lib/courrier/email/providers/ses.rb +2 -0
- data/lib/courrier/email/providers/smtpcom.rb +57 -0
- data/lib/courrier/email/providers/sparkpost.rb +23 -8
- data/lib/courrier/email/providers/userlist.rb +2 -0
- data/lib/courrier/email/rendering.rb +83 -0
- data/lib/courrier/email.rb +14 -49
- data/lib/courrier/safe_string.rb +9 -0
- data/lib/courrier/subscriber/brevo.rb +39 -0
- data/lib/courrier/version.rb +1 -1
- metadata +12 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 802112a2d410713a25a67fca11af1a9021cf8f5bd15efe55590bb6d41dcf188b
|
|
4
|
+
data.tar.gz: ac7fd7ab96871921876c79e20a623baa5d7a8fd9ab5ad13b23e3ae9e2631cc57
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 350843ff6b853bb92e797d87dd5b485ad095d3f8a70a2129863ea141e11dcf6f0fd90c8a680bf5f731ce38690e0bc0f47dba60ca61cb3ab9089d7b8d524bfaf8
|
|
7
|
+
data.tar.gz: 222f3088acd972aa5f282e4a29d5f7a2035b2373c10804ff97db89651cb09ead6938117734144c182b2b7268ff64285833674782b38d966145635818a49d2a14
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -96,6 +96,7 @@ Courrier.configure do |config|
|
|
|
96
96
|
config.providers.cloudflare.account_id = "your-account-id"
|
|
97
97
|
config.providers.loops.transactional_id = "default-template"
|
|
98
98
|
config.providers.mailgun.domain = "notifications.railsdesigner.com"
|
|
99
|
+
config.providers.smtpcom.channel = "your-sender-channel"
|
|
99
100
|
|
|
100
101
|
config.providers.ses.region = "us-east-1"
|
|
101
102
|
config.providers.ses.access_key_id = "your-access-key-id"
|
|
@@ -121,12 +122,10 @@ OrderEmail.deliver to: "recipient@railsdesigner.com",\
|
|
|
121
122
|
```
|
|
122
123
|
|
|
123
124
|
|
|
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.
|
|
125
|
-
|
|
126
125
|
|
|
127
126
|
## Custom headers
|
|
128
127
|
|
|
129
|
-
Email classes can define custom
|
|
128
|
+
Email classes can define custom headers that are sent with every email:
|
|
130
129
|
```ruby
|
|
131
130
|
class OrderEmail < Courrier::Email
|
|
132
131
|
headers list_unsubscribe_post: "List-Unsubscribe=One-Click"
|
|
@@ -194,71 +193,34 @@ end
|
|
|
194
193
|
Courrier supports these transactional email providers:
|
|
195
194
|
|
|
196
195
|
- [AWS SES](https://aws.amazon.com/ses/) — requires `aws-sigv4` gem
|
|
196
|
+
- [Brevo](https://www.brevo.com/)
|
|
197
197
|
- [Cloudflare Email Service](https://developers.cloudflare.com/email-service/)
|
|
198
198
|
- [Lettermint](https://lettermint.co)
|
|
199
199
|
- [Loops](https://loops.so)
|
|
200
|
+
- [MailerSend](https://www.mailersend.com/)
|
|
200
201
|
- [Mailgun](https://mailgun.com)
|
|
202
|
+
- [MailKite](https://mailkite.dev)
|
|
201
203
|
- [MailPace](https://mailpace.com)
|
|
204
|
+
- [Mailtrap](https://mailtrap.io)
|
|
202
205
|
- [Postmark](https://postmarkapp.com)
|
|
203
206
|
- [Resend](https://resend.com)
|
|
204
207
|
- [SendGrid](https://sendgrid.com)
|
|
205
208
|
- [SMTP2GO](https://www.smtp2go.com/)
|
|
209
|
+
- [SMTP.com](https://www.smtp.com/) — requires a `channel`
|
|
206
210
|
- [SparkPost](https://www.sparkpost.com/)
|
|
207
211
|
- [Userlist](https://userlist.com)
|
|
208
212
|
|
|
209
213
|
|
|
210
214
|
## More Features
|
|
211
215
|
|
|
212
|
-
Additional functionality to
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
### Layout support
|
|
216
|
-
|
|
217
|
-
Wrap your email content using layouts:
|
|
218
|
-
```ruby
|
|
219
|
-
class OrderEmail < Courrier::Email
|
|
220
|
-
layout text: "%{content}\n\nThanks for your order!",
|
|
221
|
-
html: "<div>\n%{content}\n</div>"
|
|
222
|
-
end
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
Using a method:
|
|
226
|
-
```ruby
|
|
227
|
-
class OrderEmail < Courrier::Email
|
|
228
|
-
layout html: :html_layout
|
|
229
|
-
|
|
230
|
-
def html_layout
|
|
231
|
-
<<~HTML
|
|
232
|
-
<div style='font-family: ui-sans-serif, system-ui;'>
|
|
233
|
-
%{content}
|
|
234
|
-
</div>
|
|
235
|
-
HTML
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
Using a separate class:
|
|
241
|
-
```ruby
|
|
242
|
-
class OrderEmail < Courrier::Email
|
|
243
|
-
layout html: OrderLayout
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
class OrderLayout
|
|
247
|
-
self.call
|
|
248
|
-
<<~HTML
|
|
249
|
-
<div style='font-family: ui-sans-serif, system-ui;'>
|
|
250
|
-
%{content}
|
|
251
|
-
</div>
|
|
252
|
-
HTML
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
```
|
|
216
|
+
Additional functionality to go beyond the basics:
|
|
256
217
|
|
|
257
218
|
|
|
258
219
|
### Template files
|
|
259
220
|
|
|
260
221
|
Instead of defining `text` and `html` methods, you can create ERB template files:
|
|
261
222
|
```ruby
|
|
223
|
+
# courrier/emails/order_email.rb
|
|
262
224
|
class OrderEmail < Courrier::Email
|
|
263
225
|
def subject = "Your order is ready!"
|
|
264
226
|
|
|
@@ -291,7 +253,7 @@ end
|
|
|
291
253
|
|
|
292
254
|
## Markdown support
|
|
293
255
|
|
|
294
|
-
Courrier supports rendering markdown
|
|
256
|
+
Courrier supports rendering markdown to HTML. Bundle any supported markdown gem (`redcarpet`, `kramdown` or `commonmarker`) and it will be used.
|
|
295
257
|
|
|
296
258
|
|
|
297
259
|
### Markdown methods
|
|
@@ -336,6 +298,49 @@ Your order **#<%= order_id %>** is ready for pickup.
|
|
|
336
298
|
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
299
|
|
|
338
300
|
|
|
301
|
+
### Layout support
|
|
302
|
+
|
|
303
|
+
Wrap your email content using layouts:
|
|
304
|
+
```ruby
|
|
305
|
+
class OrderEmail < Courrier::Email
|
|
306
|
+
layout text: "%{content}\n\nThanks for your order!",
|
|
307
|
+
html: "<div>\n%{content}\n</div>"
|
|
308
|
+
end
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Using a method:
|
|
312
|
+
```ruby
|
|
313
|
+
class OrderEmail < Courrier::Email
|
|
314
|
+
layout html: :html_layout
|
|
315
|
+
|
|
316
|
+
def html_layout
|
|
317
|
+
<<~HTML
|
|
318
|
+
<div style='font-family: ui-sans-serif, system-ui;'>
|
|
319
|
+
%{content}
|
|
320
|
+
</div>
|
|
321
|
+
HTML
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Using a separate class:
|
|
327
|
+
```ruby
|
|
328
|
+
class OrderEmail < Courrier::Email
|
|
329
|
+
layout html: OrderLayout
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
class OrderLayout
|
|
333
|
+
self.call
|
|
334
|
+
<<~HTML
|
|
335
|
+
<div style='font-family: ui-sans-serif, system-ui;'>
|
|
336
|
+
%{content}
|
|
337
|
+
</div>
|
|
338
|
+
HTML
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
|
|
339
344
|
### Auto-generate text from HTML
|
|
340
345
|
|
|
341
346
|
Automatically generate plain text versions from your HTML emails:
|
|
@@ -344,6 +349,22 @@ config.auto_generate_text = true # defaults to false
|
|
|
344
349
|
```
|
|
345
350
|
|
|
346
351
|
|
|
352
|
+
### Delivery callbacks
|
|
353
|
+
|
|
354
|
+
Hook into the email delivery lifecycle with `before_deliver` and `after_deliver` callbacks:
|
|
355
|
+
```ruby
|
|
356
|
+
class OrderEmail < Courrier::Email
|
|
357
|
+
before_deliver do |email|
|
|
358
|
+
puts "Sending to #{email.options.to}" # access email options, abort delivery by returning false
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
after_deliver do |email, result|
|
|
362
|
+
puts "Delivered: #{result.success?}" # access email and delivery result
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
|
|
347
368
|
### Email address helper
|
|
348
369
|
|
|
349
370
|
Compose email addresses with display names:
|
|
@@ -365,7 +386,7 @@ end
|
|
|
365
386
|
Use Ruby's built-in Logger for development and testing:
|
|
366
387
|
|
|
367
388
|
```ruby
|
|
368
|
-
config.
|
|
389
|
+
config.email = { provider: "logger" } # outputs emails to STDOUT
|
|
369
390
|
config.logger = custom_logger # optional: defaults to ::Logger.new($stdout)
|
|
370
391
|
```
|
|
371
392
|
|
|
@@ -385,7 +406,7 @@ end
|
|
|
385
406
|
|
|
386
407
|
Then configure it:
|
|
387
408
|
```ruby
|
|
388
|
-
config.
|
|
409
|
+
config.email = { provider: "CustomProvider" }
|
|
389
410
|
```
|
|
390
411
|
|
|
391
412
|
Check the [existing providers](https://github.com/Rails-Designer/courrier/tree/main/lib/courrier/email/providers) for implementation examples.
|
|
@@ -449,27 +470,9 @@ Available assertions:
|
|
|
449
470
|
- `assert_email_delivered(email_class, to:, from:, subject:, provider:)`; assert an email matching criteria was delivered
|
|
450
471
|
|
|
451
472
|
|
|
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
|
-
|
|
470
473
|
## Newsletter subscriptions
|
|
471
474
|
|
|
472
|
-
|
|
475
|
+
Use Courrier to manage subscribers across popular email marketing platforms:
|
|
473
476
|
```ruby
|
|
474
477
|
Courrier.configure do |config|
|
|
475
478
|
config.subscriber = {
|
|
@@ -497,6 +500,7 @@ end
|
|
|
497
500
|
### Supported providers
|
|
498
501
|
|
|
499
502
|
- [Beehiiv](https://www.beehiiv.com/) - requires `publication_id`
|
|
503
|
+
- [Brevo](https://www.brevo.com/) - accepts optional `list_ids`
|
|
500
504
|
- [Buttondown](https://buttondown.com)
|
|
501
505
|
- [Kit](https://kit.com/) (formerly ConvertKit) - requires `form_id`
|
|
502
506
|
- [Loops](https://loops.so/)
|
|
@@ -513,6 +517,14 @@ config.subscriber = {
|
|
|
513
517
|
}
|
|
514
518
|
```
|
|
515
519
|
|
|
520
|
+
For Brevo, pass one or more list IDs when new contacts should be added to specific lists:
|
|
521
|
+
```ruby
|
|
522
|
+
config.subscriber = {
|
|
523
|
+
provider: "brevo",
|
|
524
|
+
api_key: "your_api_key",
|
|
525
|
+
list_ids: [12, 34]
|
|
526
|
+
}
|
|
527
|
+
```
|
|
516
528
|
|
|
517
529
|
### Custom providers
|
|
518
530
|
|
data/courrier.gemspec
CHANGED
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
|
|
19
19
|
spec.files = Dir["{bin,lib}/**/*", "Rakefile", "README.md", "courrier.gemspec", "Gemfile", "Gemfile.lock"]
|
|
20
20
|
|
|
21
|
-
spec.required_ruby_version = ">= 4.0
|
|
21
|
+
spec.required_ruby_version = ">= 3.4.0"
|
|
22
22
|
|
|
23
23
|
spec.add_dependency "logger", ">= 1.5", "< 3"
|
|
24
24
|
spec.add_dependency "nokogiri", ">= 1.18", "< 2"
|
|
@@ -42,30 +42,6 @@ module Courrier
|
|
|
42
42
|
@providers = Courrier::Configuration::Providers.new
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
def provider
|
|
46
|
-
warn "[DEPRECATION] `provider` is deprecated. Use `email = { provider: '…' }` instead. Will be removed in 1.0.0"
|
|
47
|
-
|
|
48
|
-
@email[:provider]
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def provider=(value)
|
|
52
|
-
warn "[DEPRECATION] `provider=` is deprecated. Use `email = { provider: '…' }` instead. Will be removed in 1.0.0"
|
|
53
|
-
|
|
54
|
-
@email[:provider] = value
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def api_key
|
|
58
|
-
warn "[DEPRECATION] `api_key` is deprecated. Use `email = { api_key: '…' }` instead. Will be removed in 1.0.0"
|
|
59
|
-
|
|
60
|
-
@email[:api_key]
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def api_key=(value)
|
|
64
|
-
warn "[DEPRECATION] `api_key=` is deprecated. Use `email = { api_key: '…' }` instead. Will be removed in 1.0.0"
|
|
65
|
-
|
|
66
|
-
@email[:api_key] = value
|
|
67
|
-
end
|
|
68
|
-
|
|
69
45
|
private
|
|
70
46
|
|
|
71
47
|
def default_email_path
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
5
|
+
require "courrier/safe_string"
|
|
6
|
+
|
|
7
|
+
module Courrier
|
|
8
|
+
class ContextValue
|
|
9
|
+
def initialize(value, escape:)
|
|
10
|
+
@value = value
|
|
11
|
+
@escape = escape
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_s
|
|
15
|
+
@escape ? CGI.escapeHTML(@value.to_s) : @value.to_s
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def html_safe
|
|
19
|
+
SafeString.new(@value.to_s)
|
|
20
|
+
end
|
|
21
|
+
alias_method :h, :html_safe
|
|
22
|
+
|
|
23
|
+
def method_missing(name, *arguments, &block)
|
|
24
|
+
result = @value.send(name, *arguments, &block)
|
|
25
|
+
|
|
26
|
+
result.is_a?(String) ? ContextValue.new(result, escape: @escape) : result
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def respond_to_missing?(name, include_private = false)
|
|
30
|
+
@value.respond_to?(name, include_private) || super
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -15,9 +15,9 @@ module Courrier
|
|
|
15
15
|
@cc = options.fetch(:cc, nil)
|
|
16
16
|
@bcc = options.fetch(:bcc, nil)
|
|
17
17
|
|
|
18
|
-
@subject = options.fetch(:subject, "")
|
|
19
|
-
@text = options.fetch(:text, "")
|
|
20
|
-
@html = options.fetch(:html, "")
|
|
18
|
+
@subject = options.fetch(:subject, "").to_s
|
|
19
|
+
@text = options.fetch(:text, "").to_s
|
|
20
|
+
@html = options.fetch(:html, "").to_s
|
|
21
21
|
|
|
22
22
|
@auto_generate_text = options.fetch(:auto_generate_text, false)
|
|
23
23
|
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "courrier/email/providers/base"
|
|
4
|
+
require "courrier/email/providers/brevo"
|
|
4
5
|
require "courrier/email/providers/cloudflare"
|
|
5
6
|
require "courrier/email/providers/lettermint"
|
|
6
7
|
require "courrier/email/providers/logger"
|
|
7
8
|
require "courrier/email/providers/loops"
|
|
9
|
+
require "courrier/email/providers/mailersend"
|
|
8
10
|
require "courrier/email/providers/mailgun"
|
|
11
|
+
require "courrier/email/providers/mailkite"
|
|
9
12
|
require "courrier/email/providers/mailjet"
|
|
10
13
|
require "courrier/email/providers/mailpace"
|
|
14
|
+
require "courrier/email/providers/mailtrap"
|
|
11
15
|
require "courrier/email/providers/postmark"
|
|
12
16
|
require "courrier/email/providers/resend"
|
|
13
17
|
require "courrier/email/providers/ses"
|
|
14
18
|
require "courrier/email/providers/sendgrid"
|
|
15
19
|
require "courrier/email/providers/smtp2go"
|
|
20
|
+
require "courrier/email/providers/smtpcom"
|
|
16
21
|
require "courrier/email/providers/sparkpost"
|
|
17
22
|
require "courrier/email/providers/userlist"
|
|
18
23
|
|
|
@@ -20,18 +25,23 @@ module Courrier
|
|
|
20
25
|
class Email
|
|
21
26
|
class Provider
|
|
22
27
|
PROVIDERS = {
|
|
28
|
+
brevo: Courrier::Email::Providers::Brevo,
|
|
23
29
|
cloudflare: Courrier::Email::Providers::Cloudflare,
|
|
24
30
|
logger: Courrier::Email::Providers::Logger,
|
|
25
31
|
lettermint: Courrier::Email::Providers::Lettermint,
|
|
26
32
|
loops: Courrier::Email::Providers::Loops,
|
|
33
|
+
mailersend: Courrier::Email::Providers::Mailersend,
|
|
27
34
|
mailgun: Courrier::Email::Providers::Mailgun,
|
|
35
|
+
mailkite: Courrier::Email::Providers::Mailkite,
|
|
28
36
|
mailjet: Courrier::Email::Providers::Mailjet,
|
|
29
37
|
mailpace: Courrier::Email::Providers::Mailpace,
|
|
38
|
+
mailtrap: Courrier::Email::Providers::Mailtrap,
|
|
30
39
|
postmark: Courrier::Email::Providers::Postmark,
|
|
31
40
|
resend: Courrier::Email::Providers::Resend,
|
|
32
41
|
ses: Courrier::Email::Providers::Ses,
|
|
33
42
|
sendgrid: Courrier::Email::Providers::Sendgrid,
|
|
34
43
|
smtp2go: Courrier::Email::Providers::Smtp2go,
|
|
44
|
+
smtpcom: Courrier::Email::Providers::Smtpcom,
|
|
35
45
|
sparkpost: Courrier::Email::Providers::Sparkpost,
|
|
36
46
|
userlist: Courrier::Email::Providers::Userlist
|
|
37
47
|
}
|
|
@@ -6,6 +6,8 @@ module Courrier
|
|
|
6
6
|
class Email
|
|
7
7
|
module Providers
|
|
8
8
|
class Base
|
|
9
|
+
def self.config_options = []
|
|
10
|
+
|
|
9
11
|
def initialize(api_key: nil, options: {}, provider_options: {}, context_options: {}, custom_headers: {})
|
|
10
12
|
@api_key = api_key
|
|
11
13
|
@options = options
|
|
@@ -38,6 +40,23 @@ module Courrier
|
|
|
38
40
|
|
|
39
41
|
def default_headers = {}
|
|
40
42
|
|
|
43
|
+
def address_list(value, as: :email)
|
|
44
|
+
list = value&.to_s&.split(",")&.map(&:strip)&.reject(&:empty?)
|
|
45
|
+
return unless list && !list.empty?
|
|
46
|
+
|
|
47
|
+
list.map { |address| address_element(address, as) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def address_line(value) = address_list(value, as: :plain)&.join(", ")
|
|
51
|
+
|
|
52
|
+
def address_element(address, as)
|
|
53
|
+
case as
|
|
54
|
+
when :email then {"email" => address}
|
|
55
|
+
when :address then {"address" => address}
|
|
56
|
+
else address
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
41
60
|
def provider = self.class.name.split("::").last
|
|
42
61
|
end
|
|
43
62
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Email
|
|
5
|
+
module Providers
|
|
6
|
+
class Brevo < Base
|
|
7
|
+
ENDPOINT_URL = "https://api.brevo.com/v3/smtp/email"
|
|
8
|
+
|
|
9
|
+
def body
|
|
10
|
+
{
|
|
11
|
+
"sender" => address_list(@options.from)&.first,
|
|
12
|
+
"to" => address_list(@options.to),
|
|
13
|
+
"cc" => address_list(@options.cc),
|
|
14
|
+
"bcc" => address_list(@options.bcc),
|
|
15
|
+
"replyTo" => address_list(@options.reply_to)&.first,
|
|
16
|
+
"subject" => @options.subject,
|
|
17
|
+
"htmlContent" => @options.html,
|
|
18
|
+
"textContent" => @options.text
|
|
19
|
+
}.compact
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def default_headers = {"api-key" => @api_key}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Email
|
|
5
|
+
module Providers
|
|
6
|
+
class Mailersend < Base
|
|
7
|
+
ENDPOINT_URL = "https://api.mailersend.com/v1/email"
|
|
8
|
+
|
|
9
|
+
def body
|
|
10
|
+
{
|
|
11
|
+
"from" => address_list(@options.from)&.first,
|
|
12
|
+
"to" => address_list(@options.to),
|
|
13
|
+
"cc" => address_list(@options.cc),
|
|
14
|
+
"bcc" => address_list(@options.bcc),
|
|
15
|
+
"reply_to" => address_list(@options.reply_to)&.first,
|
|
16
|
+
"subject" => @options.subject,
|
|
17
|
+
"text" => @options.text,
|
|
18
|
+
"html" => @options.html
|
|
19
|
+
}.compact
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def default_headers = {"Authorization" => "Bearer #{@api_key}"}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -4,6 +4,8 @@ module Courrier
|
|
|
4
4
|
class Email
|
|
5
5
|
module Providers
|
|
6
6
|
class Mailgun < Base
|
|
7
|
+
def self.config_options = %w[domain]
|
|
8
|
+
|
|
7
9
|
ENDPOINT_URL = "https://api.mailgun.net/v3/%{domain}/messages"
|
|
8
10
|
|
|
9
11
|
def body
|
|
@@ -11,6 +13,8 @@ module Courrier
|
|
|
11
13
|
"from" => @options.from,
|
|
12
14
|
|
|
13
15
|
"to" => @options.to,
|
|
16
|
+
"cc" => address_line(@options.cc),
|
|
17
|
+
"bcc" => address_line(@options.bcc),
|
|
14
18
|
"h:Reply-To" => @options.reply_to,
|
|
15
19
|
|
|
16
20
|
"subject" => @options.subject,
|
|
@@ -31,7 +35,7 @@ module Courrier
|
|
|
31
35
|
|
|
32
36
|
def default_headers
|
|
33
37
|
{
|
|
34
|
-
"Authorization" => "Basic #{
|
|
38
|
+
"Authorization" => "Basic #{["api:#{@api_key}"].pack("m0")}"
|
|
35
39
|
}
|
|
36
40
|
end
|
|
37
41
|
end
|
|
@@ -4,6 +4,8 @@ module Courrier
|
|
|
4
4
|
class Email
|
|
5
5
|
module Providers
|
|
6
6
|
class Mailjet < Base
|
|
7
|
+
def self.config_options = %w[api_secret]
|
|
8
|
+
|
|
7
9
|
ENDPOINT_URL = "https://api.mailjet.com/v3.1/send"
|
|
8
10
|
|
|
9
11
|
def body
|
|
@@ -14,11 +16,9 @@ module Courrier
|
|
|
14
16
|
"Email" => @options.from
|
|
15
17
|
},
|
|
16
18
|
|
|
17
|
-
"To" =>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
],
|
|
19
|
+
"To" => addresses(@options.to),
|
|
20
|
+
"Cc" => addresses(@options.cc),
|
|
21
|
+
"Bcc" => addresses(@options.bcc),
|
|
22
22
|
"ReplyTo" => reply_to_object,
|
|
23
23
|
|
|
24
24
|
"Subject" => @options.subject,
|
|
@@ -33,10 +33,14 @@ module Courrier
|
|
|
33
33
|
|
|
34
34
|
def default_headers
|
|
35
35
|
{
|
|
36
|
-
"Authorization" => "Basic "
|
|
36
|
+
"Authorization" => "Basic #{["#{@api_key}:#{@provider_options.api_secret}"].pack("m0")}"
|
|
37
37
|
}
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
def addresses(value)
|
|
41
|
+
address_list(value, as: :plain)&.map { |address| {"Email" => address} }
|
|
42
|
+
end
|
|
43
|
+
|
|
40
44
|
def reply_to_object
|
|
41
45
|
return if @options.reply_to.nil?
|
|
42
46
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Email
|
|
5
|
+
module Providers
|
|
6
|
+
class Mailkite < Base
|
|
7
|
+
ENDPOINT_URL = "https://api.mailkite.dev/v1/send"
|
|
8
|
+
|
|
9
|
+
def body
|
|
10
|
+
{
|
|
11
|
+
"from" => @options.from,
|
|
12
|
+
"to" => address_list(@options.to, as: :plain),
|
|
13
|
+
"cc" => address_list(@options.cc, as: :plain),
|
|
14
|
+
"bcc" => address_list(@options.bcc, as: :plain),
|
|
15
|
+
"replyTo" => @options.reply_to,
|
|
16
|
+
"subject" => @options.subject,
|
|
17
|
+
"text" => @options.text,
|
|
18
|
+
"html" => @options.html
|
|
19
|
+
}.compact
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def default_headers
|
|
25
|
+
{
|
|
26
|
+
"Authorization" => "Bearer #{@api_key}"
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Email
|
|
5
|
+
module Providers
|
|
6
|
+
class Mailtrap < Base
|
|
7
|
+
ENDPOINT_URL = "https://send.api.mailtrap.io/api/send"
|
|
8
|
+
|
|
9
|
+
def body
|
|
10
|
+
{
|
|
11
|
+
"from" => address_list(@options.from)&.first,
|
|
12
|
+
"to" => address_list(@options.to),
|
|
13
|
+
"cc" => address_list(@options.cc),
|
|
14
|
+
"bcc" => address_list(@options.bcc),
|
|
15
|
+
"reply_to" => address_list(@options.reply_to)&.first,
|
|
16
|
+
"subject" => @options.subject,
|
|
17
|
+
"text" => @options.text,
|
|
18
|
+
"html" => @options.html
|
|
19
|
+
}.compact
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def default_headers = {"Api-Token" => @api_key}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -4,6 +4,8 @@ module Courrier
|
|
|
4
4
|
class Email
|
|
5
5
|
module Providers
|
|
6
6
|
class Postmark < Base
|
|
7
|
+
def self.config_options = %w[message_stream]
|
|
8
|
+
|
|
7
9
|
ENDPOINT_URL = "https://api.postmarkapp.com/email"
|
|
8
10
|
|
|
9
11
|
def body
|
|
@@ -11,6 +13,8 @@ module Courrier
|
|
|
11
13
|
"From" => @options.from,
|
|
12
14
|
|
|
13
15
|
"To" => @options.to,
|
|
16
|
+
"Cc" => address_line(@options.cc),
|
|
17
|
+
"Bcc" => address_line(@options.bcc),
|
|
14
18
|
"ReplyTo" => @options.reply_to,
|
|
15
19
|
|
|
16
20
|
"Subject" => @options.subject,
|
|
@@ -13,12 +13,10 @@ module Courrier
|
|
|
13
13
|
},
|
|
14
14
|
"personalizations" => [
|
|
15
15
|
{
|
|
16
|
-
"to" =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
]
|
|
21
|
-
}
|
|
16
|
+
"to" => address_list(@options.to),
|
|
17
|
+
"cc" => address_list(@options.cc),
|
|
18
|
+
"bcc" => address_list(@options.bcc)
|
|
19
|
+
}.compact
|
|
22
20
|
],
|
|
23
21
|
"reply_to" => reply_to_object,
|
|
24
22
|
|
|
@@ -4,6 +4,8 @@ module Courrier
|
|
|
4
4
|
class Email
|
|
5
5
|
module Providers
|
|
6
6
|
class Ses < Base
|
|
7
|
+
def self.config_options = %w[region access_key_id secret_access_key session_token]
|
|
8
|
+
|
|
7
9
|
def deliver
|
|
8
10
|
uri = URI.parse("https://email.#{@provider_options[:region]}.amazonaws.com/v2/email/outbound-emails")
|
|
9
11
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Email
|
|
5
|
+
module Providers
|
|
6
|
+
class Smtpcom < Base
|
|
7
|
+
def self.config_options = %w[channel]
|
|
8
|
+
|
|
9
|
+
ENDPOINT_URL = "https://api.smtp.com/v4/messages"
|
|
10
|
+
|
|
11
|
+
def body
|
|
12
|
+
{
|
|
13
|
+
"channel" => channel,
|
|
14
|
+
"recipients" => {
|
|
15
|
+
"to" => address_list(@options.to, as: :address),
|
|
16
|
+
"cc" => address_list(@options.cc, as: :address),
|
|
17
|
+
"bcc" => address_list(@options.bcc, as: :address)
|
|
18
|
+
}.compact,
|
|
19
|
+
"originator" => {
|
|
20
|
+
"from" => address_list(@options.from, as: :address)&.first,
|
|
21
|
+
"reply_to" => address_list(@options.reply_to, as: :address)&.first
|
|
22
|
+
}.compact,
|
|
23
|
+
"subject" => @options.subject,
|
|
24
|
+
"body" => {"parts" => parts}
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def default_headers
|
|
31
|
+
{"Authorization" => "Basic #{base64("api:#{@api_key}")}"}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def channel
|
|
35
|
+
@provider_options.channel || raise(Courrier::ArgumentError, "SMTP.com requires a `channel`")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parts
|
|
39
|
+
[part("text/plain", @options.text), part("text/html", @options.html)].compact
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def part(type, content)
|
|
43
|
+
return unless content
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
"type" => type,
|
|
47
|
+
"charset" => "UTF-8",
|
|
48
|
+
"encoding" => "base64",
|
|
49
|
+
"content" => base64(content)
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def base64(value) = [value].pack("m0")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -13,15 +13,10 @@ module Courrier
|
|
|
13
13
|
"from" => @options.from,
|
|
14
14
|
"subject" => @options.subject,
|
|
15
15
|
"text" => @options.text,
|
|
16
|
-
"html" => @options.html
|
|
16
|
+
"html" => @options.html,
|
|
17
|
+
"headers" => cc_header
|
|
17
18
|
}.compact,
|
|
18
|
-
"recipients" =>
|
|
19
|
-
{
|
|
20
|
-
"address" => {
|
|
21
|
-
"email" => @options.to
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
]
|
|
19
|
+
"recipients" => recipients
|
|
25
20
|
}
|
|
26
21
|
end
|
|
27
22
|
|
|
@@ -32,6 +27,26 @@ module Courrier
|
|
|
32
27
|
"Authorization" => @api_key
|
|
33
28
|
}
|
|
34
29
|
end
|
|
30
|
+
|
|
31
|
+
# SparkPost has no cc/bcc field: every copy is an entry in `recipients`, and only
|
|
32
|
+
# the addresses repeated in the CC header are shown as copied. `header_to` keeps the
|
|
33
|
+
# visible To line the same for all of them, so a bcc stays hidden and a cc is not
|
|
34
|
+
# mistaken for a to.
|
|
35
|
+
def recipients
|
|
36
|
+
[@options.to, @options.cc, @options.bcc].flat_map { |value| recipients_for(value) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def recipients_for(value)
|
|
40
|
+
Array(address_list(value, as: :plain)).map do |address|
|
|
41
|
+
{"address" => {"email" => address, "header_to" => address_line(@options.to)}}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def cc_header
|
|
46
|
+
cc = address_line(@options.cc)
|
|
47
|
+
|
|
48
|
+
{"CC" => cc} if cc
|
|
49
|
+
end
|
|
35
50
|
end
|
|
36
51
|
end
|
|
37
52
|
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
|
|
5
|
+
require "courrier/context_value"
|
|
6
|
+
require "courrier/safe_string"
|
|
7
|
+
require "courrier/markdown"
|
|
8
|
+
|
|
9
|
+
module Courrier
|
|
10
|
+
class Email
|
|
11
|
+
module Rendering
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def method_missing(name, *)
|
|
15
|
+
return in_html_context { render_template("html") || markdown_rendered } if name == :html
|
|
16
|
+
return render_template("text") if name == :text
|
|
17
|
+
|
|
18
|
+
value = @context_options[name]
|
|
19
|
+
return value if value.is_a?(SafeString)
|
|
20
|
+
|
|
21
|
+
value.is_a?(String) ? ContextValue.new(value, escape: @html_context) : value
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def in_html_context
|
|
25
|
+
previous = @html_context
|
|
26
|
+
@html_context = true
|
|
27
|
+
|
|
28
|
+
yield
|
|
29
|
+
ensure
|
|
30
|
+
@html_context = previous
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def in_safe_context
|
|
34
|
+
previous = @html_context
|
|
35
|
+
@html_context = false
|
|
36
|
+
|
|
37
|
+
yield
|
|
38
|
+
ensure
|
|
39
|
+
@html_context = previous
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def render_template(format)
|
|
43
|
+
template_path = template_file_path(format)
|
|
44
|
+
|
|
45
|
+
File.exist?(template_path) ? ERB.new(File.read(template_path)).result(binding) : nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def render_markdown_template
|
|
49
|
+
%w[md markdown].each do |ext|
|
|
50
|
+
template_path = template_file_path(ext)
|
|
51
|
+
|
|
52
|
+
return ERB.new(File.read(template_path)).result(binding) if File.exist?(template_path)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def markdown_rendered
|
|
59
|
+
return unless Courrier::Markdown.available?
|
|
60
|
+
|
|
61
|
+
markdown_content = render_markdown_template || (respond_to?(:markdown, true) ? markdown : nil)
|
|
62
|
+
|
|
63
|
+
Courrier::Markdown.render(markdown_content) if markdown_content
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def template_file_path(format)
|
|
67
|
+
class_path = self.class.name.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
|
68
|
+
|
|
69
|
+
File.join(Courrier.configuration&.email_path, "#{class_path}.#{format}.erb")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def html_safe(value = nil, &block)
|
|
73
|
+
return in_safe_context { SafeString.new(block.call.to_s) } if block
|
|
74
|
+
return value.html_safe if value.is_a?(ContextValue)
|
|
75
|
+
|
|
76
|
+
SafeString.new(value.to_s)
|
|
77
|
+
end
|
|
78
|
+
alias_method :h, :html_safe
|
|
79
|
+
|
|
80
|
+
def respond_to_missing?(name, include_private = false) = true
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/courrier/email.rb
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
4
|
-
|
|
3
|
+
require "courrier/email/rendering"
|
|
5
4
|
require "courrier/email/address"
|
|
6
5
|
require "courrier/email/layouts"
|
|
7
|
-
require "courrier/markdown"
|
|
8
6
|
require "courrier/email/options"
|
|
9
7
|
require "courrier/email/provider"
|
|
10
8
|
|
|
11
9
|
module Courrier
|
|
12
10
|
class Email
|
|
11
|
+
include Rendering
|
|
12
|
+
|
|
13
13
|
attr_accessor :provider, :api_key, :default_url_options, :options, :queue_options
|
|
14
14
|
|
|
15
15
|
@queue_options = {}
|
|
@@ -60,11 +60,17 @@ module Courrier
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def before_deliver_callbacks
|
|
63
|
-
@before_deliver
|
|
63
|
+
return @before_deliver if @before_deliver
|
|
64
|
+
return superclass.before_deliver_callbacks if superclass.respond_to?(:before_deliver_callbacks)
|
|
65
|
+
|
|
66
|
+
[]
|
|
64
67
|
end
|
|
65
68
|
|
|
66
69
|
def after_deliver_callbacks
|
|
67
|
-
@after_deliver
|
|
70
|
+
return @after_deliver if @after_deliver
|
|
71
|
+
return superclass.after_deliver_callbacks if superclass.respond_to?(:after_deliver_callbacks)
|
|
72
|
+
|
|
73
|
+
[]
|
|
68
74
|
end
|
|
69
75
|
|
|
70
76
|
def deliver(**options)
|
|
@@ -78,8 +84,8 @@ module Courrier
|
|
|
78
84
|
end
|
|
79
85
|
|
|
80
86
|
def initialize(options = {})
|
|
81
|
-
@provider = options[:provider] ||
|
|
82
|
-
@api_key = options[:api_key] ||
|
|
87
|
+
@provider = options[:provider] || self.class.provider || Courrier.configuration&.email&.[](:provider)
|
|
88
|
+
@api_key = options[:api_key] || self.class.api_key || Courrier.configuration&.email&.[](:api_key)
|
|
83
89
|
|
|
84
90
|
@default_url_options = self.class.default_url_options.merge(options[:default_url_options] || {})
|
|
85
91
|
@context_options = options.except(:provider, :api_key, :from, :to, :reply_to, :cc, :bcc, :subject, :text, :html)
|
|
@@ -91,7 +97,7 @@ module Courrier
|
|
|
91
97
|
bcc: options[:bcc] || self.class.bcc || Courrier.configuration&.bcc,
|
|
92
98
|
subject: subject,
|
|
93
99
|
text: text,
|
|
94
|
-
html: html,
|
|
100
|
+
html: in_html_context { html },
|
|
95
101
|
auto_generate_text: Courrier.configuration&.auto_generate_text,
|
|
96
102
|
layouts: Courrier::Email::Layouts.new(self).build
|
|
97
103
|
)
|
|
@@ -127,46 +133,5 @@ module Courrier
|
|
|
127
133
|
def delivery_disabled?
|
|
128
134
|
ENV["COURRIER_EMAIL_DISABLED"] == "true" || ENV["COURRIER_EMAIL_ENABLED"] == "false"
|
|
129
135
|
end
|
|
130
|
-
|
|
131
|
-
def method_missing(name, *)
|
|
132
|
-
if name == :text || name == :html
|
|
133
|
-
render_template(name.to_s).tap do |result|
|
|
134
|
-
return result || markdown_rendered if name == :html
|
|
135
|
-
end
|
|
136
|
-
else
|
|
137
|
-
@context_options[name]
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def render_template(format)
|
|
142
|
-
template_path = template_file_path(format)
|
|
143
|
-
|
|
144
|
-
File.exist?(template_path) ? ERB.new(File.read(template_path)).result(binding) : nil
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def render_markdown_template
|
|
148
|
-
%w[md markdown].each do |ext|
|
|
149
|
-
template_path = template_file_path(ext)
|
|
150
|
-
|
|
151
|
-
return ERB.new(File.read(template_path)).result(binding) if File.exist?(template_path)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
nil
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def markdown_rendered
|
|
158
|
-
return unless Courrier::Markdown.available?
|
|
159
|
-
|
|
160
|
-
markdown_content = render_markdown_template || (respond_to?(:markdown, true) ? markdown : nil)
|
|
161
|
-
Courrier::Markdown.render(markdown_content) if markdown_content
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def template_file_path(format)
|
|
165
|
-
class_path = self.class.name.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
|
166
|
-
|
|
167
|
-
File.join(Courrier.configuration&.email_path, "#{class_path}.#{format}.erb")
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def respond_to_missing?(name, include_private = false) = true
|
|
171
136
|
end
|
|
172
137
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
require "courrier/subscriber/base"
|
|
5
|
+
|
|
6
|
+
module Courrier
|
|
7
|
+
class Subscriber
|
|
8
|
+
class Brevo < Base
|
|
9
|
+
ENDPOINT_URL = "https://api.brevo.com/v3/contacts"
|
|
10
|
+
|
|
11
|
+
def create(email)
|
|
12
|
+
body = {
|
|
13
|
+
"email" => email,
|
|
14
|
+
"updateEnabled" => true
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
list_ids = Array(Courrier.configuration.subscriber[:list_ids]).compact
|
|
18
|
+
body["listIds"] = list_ids unless list_ids.empty?
|
|
19
|
+
|
|
20
|
+
request(:post, ENDPOINT_URL, body)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def destroy(email)
|
|
24
|
+
identifier = URI.encode_www_form_component(email)
|
|
25
|
+
|
|
26
|
+
request(:delete, "#{ENDPOINT_URL}/#{identifier}")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def headers
|
|
32
|
+
{
|
|
33
|
+
"api-key" => @api_key,
|
|
34
|
+
"Content-Type" => "application/json"
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/courrier/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: courrier
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rails Designer
|
|
@@ -68,34 +68,43 @@ files:
|
|
|
68
68
|
- lib/courrier.rb
|
|
69
69
|
- lib/courrier/configuration.rb
|
|
70
70
|
- lib/courrier/configuration/providers.rb
|
|
71
|
+
- lib/courrier/context_value.rb
|
|
71
72
|
- lib/courrier/email.rb
|
|
72
73
|
- lib/courrier/email/address.rb
|
|
73
74
|
- lib/courrier/email/layouts.rb
|
|
74
75
|
- lib/courrier/email/options.rb
|
|
75
76
|
- lib/courrier/email/provider.rb
|
|
76
77
|
- lib/courrier/email/providers/base.rb
|
|
78
|
+
- lib/courrier/email/providers/brevo.rb
|
|
77
79
|
- lib/courrier/email/providers/cloudflare.rb
|
|
78
80
|
- lib/courrier/email/providers/lettermint.rb
|
|
79
81
|
- lib/courrier/email/providers/logger.rb
|
|
80
82
|
- lib/courrier/email/providers/loops.rb
|
|
83
|
+
- lib/courrier/email/providers/mailersend.rb
|
|
81
84
|
- lib/courrier/email/providers/mailgun.rb
|
|
82
85
|
- lib/courrier/email/providers/mailjet.rb
|
|
86
|
+
- lib/courrier/email/providers/mailkite.rb
|
|
83
87
|
- lib/courrier/email/providers/mailpace.rb
|
|
88
|
+
- lib/courrier/email/providers/mailtrap.rb
|
|
84
89
|
- lib/courrier/email/providers/postmark.rb
|
|
85
90
|
- lib/courrier/email/providers/resend.rb
|
|
86
91
|
- lib/courrier/email/providers/sendgrid.rb
|
|
87
92
|
- lib/courrier/email/providers/ses.rb
|
|
88
93
|
- lib/courrier/email/providers/smtp2go.rb
|
|
94
|
+
- lib/courrier/email/providers/smtpcom.rb
|
|
89
95
|
- lib/courrier/email/providers/sparkpost.rb
|
|
90
96
|
- lib/courrier/email/providers/userlist.rb
|
|
97
|
+
- lib/courrier/email/rendering.rb
|
|
91
98
|
- lib/courrier/email/request.rb
|
|
92
99
|
- lib/courrier/email/result.rb
|
|
93
100
|
- lib/courrier/email/transformer.rb
|
|
94
101
|
- lib/courrier/errors.rb
|
|
95
102
|
- lib/courrier/markdown.rb
|
|
103
|
+
- lib/courrier/safe_string.rb
|
|
96
104
|
- lib/courrier/subscriber.rb
|
|
97
105
|
- lib/courrier/subscriber/base.rb
|
|
98
106
|
- lib/courrier/subscriber/beehiiv.rb
|
|
107
|
+
- lib/courrier/subscriber/brevo.rb
|
|
99
108
|
- lib/courrier/subscriber/buttondown.rb
|
|
100
109
|
- lib/courrier/subscriber/kit.rb
|
|
101
110
|
- lib/courrier/subscriber/loops.rb
|
|
@@ -118,14 +127,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
118
127
|
requirements:
|
|
119
128
|
- - ">="
|
|
120
129
|
- !ruby/object:Gem::Version
|
|
121
|
-
version: 4.0
|
|
130
|
+
version: 3.4.0
|
|
122
131
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
132
|
requirements:
|
|
124
133
|
- - ">="
|
|
125
134
|
- !ruby/object:Gem::Version
|
|
126
135
|
version: '0'
|
|
127
136
|
requirements: []
|
|
128
|
-
rubygems_version:
|
|
137
|
+
rubygems_version: 3.6.7
|
|
129
138
|
specification_version: 4
|
|
130
139
|
summary: API-powered email delivery for Ruby apps
|
|
131
140
|
test_files: []
|