courrier 0.10.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +16 -24
- data/README.md +173 -121
- data/courrier.gemspec +4 -4
- data/lib/courrier/configuration.rb +2 -28
- data/lib/courrier/email/provider.rb +12 -4
- data/lib/courrier/email/providers/base.rb +2 -0
- data/lib/courrier/email/providers/brevo.rb +36 -0
- data/lib/courrier/email/providers/cloudflare.rb +37 -0
- data/lib/courrier/email/providers/lettermint.rb +33 -0
- data/lib/courrier/email/providers/loops.rb +2 -0
- data/lib/courrier/email/providers/mailgun.rb +2 -0
- data/lib/courrier/email/providers/mailjet.rb +2 -0
- data/lib/courrier/email/providers/postmark.rb +2 -0
- data/lib/courrier/email/providers/ses.rb +77 -0
- data/lib/courrier/email/providers/smtp2go.rb +29 -0
- data/lib/courrier/email/providers/userlist.rb +2 -0
- data/lib/courrier/email/request.rb +1 -1
- data/lib/courrier/email/transformer.rb +9 -9
- data/lib/courrier/email.rb +30 -37
- data/lib/courrier/errors.rb +0 -2
- data/lib/courrier/subscriber/brevo.rb +39 -0
- data/lib/courrier/test.rb +38 -0
- data/lib/courrier/test_helper.rb +65 -0
- data/lib/courrier/version.rb +1 -1
- data/lib/courrier.rb +2 -2
- metadata +19 -31
- data/app/controllers/courrier/previews/cleanups_controller.rb +0 -13
- data/app/controllers/courrier/previews_controller.rb +0 -23
- data/app/views/courrier/previews/index.html.erb +0 -171
- data/config/routes.rb +0 -8
- data/lib/courrier/configuration/inbox.rb +0 -21
- data/lib/courrier/email/providers/inbox/default.html.erb +0 -126
- data/lib/courrier/email/providers/inbox.rb +0 -83
- data/lib/courrier/engine.rb +0 -7
- data/lib/courrier/jobs/email_delivery_job.rb +0 -23
- data/lib/courrier/railtie.rb +0 -23
- data/lib/courrier/tasks/courrier.rake +0 -13
- data/lib/generators/courrier/email_generator.rb +0 -42
- data/lib/generators/courrier/install_generator.rb +0 -11
- data/lib/generators/courrier/templates/email/password_reset.rb.tt +0 -29
- data/lib/generators/courrier/templates/email/welcome.rb.tt +0 -43
- data/lib/generators/courrier/templates/email.rb.tt +0 -13
- data/lib/generators/courrier/templates/initializer.rb.tt +0 -43
|
@@ -1,7 +1,9 @@
|
|
|
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/brevo"
|
|
5
|
+
require "courrier/email/providers/cloudflare"
|
|
6
|
+
require "courrier/email/providers/lettermint"
|
|
5
7
|
require "courrier/email/providers/logger"
|
|
6
8
|
require "courrier/email/providers/loops"
|
|
7
9
|
require "courrier/email/providers/mailgun"
|
|
@@ -9,7 +11,9 @@ require "courrier/email/providers/mailjet"
|
|
|
9
11
|
require "courrier/email/providers/mailpace"
|
|
10
12
|
require "courrier/email/providers/postmark"
|
|
11
13
|
require "courrier/email/providers/resend"
|
|
14
|
+
require "courrier/email/providers/ses"
|
|
12
15
|
require "courrier/email/providers/sendgrid"
|
|
16
|
+
require "courrier/email/providers/smtp2go"
|
|
13
17
|
require "courrier/email/providers/sparkpost"
|
|
14
18
|
require "courrier/email/providers/userlist"
|
|
15
19
|
|
|
@@ -17,15 +21,19 @@ module Courrier
|
|
|
17
21
|
class Email
|
|
18
22
|
class Provider
|
|
19
23
|
PROVIDERS = {
|
|
20
|
-
|
|
24
|
+
brevo: Courrier::Email::Providers::Brevo,
|
|
25
|
+
cloudflare: Courrier::Email::Providers::Cloudflare,
|
|
21
26
|
logger: Courrier::Email::Providers::Logger,
|
|
27
|
+
lettermint: Courrier::Email::Providers::Lettermint,
|
|
22
28
|
loops: Courrier::Email::Providers::Loops,
|
|
23
29
|
mailgun: Courrier::Email::Providers::Mailgun,
|
|
24
30
|
mailjet: Courrier::Email::Providers::Mailjet,
|
|
25
31
|
mailpace: Courrier::Email::Providers::Mailpace,
|
|
26
32
|
postmark: Courrier::Email::Providers::Postmark,
|
|
27
33
|
resend: Courrier::Email::Providers::Resend,
|
|
34
|
+
ses: Courrier::Email::Providers::Ses,
|
|
28
35
|
sendgrid: Courrier::Email::Providers::Sendgrid,
|
|
36
|
+
smtp2go: Courrier::Email::Providers::Smtp2go,
|
|
29
37
|
sparkpost: Courrier::Email::Providers::Sparkpost,
|
|
30
38
|
userlist: Courrier::Email::Providers::Userlist
|
|
31
39
|
}
|
|
@@ -72,7 +80,7 @@ module Courrier
|
|
|
72
80
|
end
|
|
73
81
|
|
|
74
82
|
def api_key_required_providers?
|
|
75
|
-
!%w[logger
|
|
83
|
+
!%w[logger].include?(@provider.to_s)
|
|
76
84
|
end
|
|
77
85
|
|
|
78
86
|
def api_key_blank?
|
|
@@ -80,7 +88,7 @@ module Courrier
|
|
|
80
88
|
end
|
|
81
89
|
|
|
82
90
|
def production?
|
|
83
|
-
|
|
91
|
+
ENV["RACK_ENV"] == "production"
|
|
84
92
|
end
|
|
85
93
|
end
|
|
86
94
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
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" => email_address(@options.from),
|
|
12
|
+
"to" => email_addresses(@options.to),
|
|
13
|
+
"cc" => email_addresses(@options.cc),
|
|
14
|
+
"bcc" => email_addresses(@options.bcc),
|
|
15
|
+
"replyTo" => email_address(@options.reply_to),
|
|
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
|
+
|
|
26
|
+
def email_addresses(addresses)
|
|
27
|
+
addresses&.split(",")&.map { |address| email_address(address) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def email_address(address)
|
|
31
|
+
{"email" => address.strip} if address
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Email
|
|
5
|
+
module Providers
|
|
6
|
+
class Cloudflare < Base
|
|
7
|
+
def self.config_options = %w[account_id]
|
|
8
|
+
|
|
9
|
+
def body
|
|
10
|
+
{
|
|
11
|
+
"from" => @options.from,
|
|
12
|
+
"to" => @options.to,
|
|
13
|
+
"reply_to" => @options.reply_to,
|
|
14
|
+
"cc" => @options.cc,
|
|
15
|
+
"bcc" => @options.bcc,
|
|
16
|
+
"subject" => @options.subject,
|
|
17
|
+
"text" => @options.text,
|
|
18
|
+
"html" => @options.html
|
|
19
|
+
}.compact
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def endpoint_url
|
|
25
|
+
account_id = @provider_options.account_id
|
|
26
|
+
raise Courrier::ArgumentError, "Cloudflare requires an `account_id`" unless account_id
|
|
27
|
+
|
|
28
|
+
"https://api.cloudflare.com/client/v4/accounts/#{account_id}/email/sending/send"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def default_headers
|
|
32
|
+
{"Authorization" => "Bearer #{@api_key}"}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Email
|
|
5
|
+
module Providers
|
|
6
|
+
class Lettermint < Base
|
|
7
|
+
def self.config_options = %w[route]
|
|
8
|
+
|
|
9
|
+
ENDPOINT_URL = "https://api.lettermint.co/v1/send"
|
|
10
|
+
|
|
11
|
+
def body
|
|
12
|
+
{
|
|
13
|
+
"route" => @provider_options.route,
|
|
14
|
+
"from" => @options.from,
|
|
15
|
+
"to" => @options.to.to_s.split(",").map(&:strip),
|
|
16
|
+
"cc" => @options.cc&.split(",")&.map(&:strip),
|
|
17
|
+
"bcc" => @options.bcc&.split(",")&.map(&:strip),
|
|
18
|
+
"reply_to" => @options.reply_to&.split(",")&.map(&:strip),
|
|
19
|
+
"subject" => @options.subject,
|
|
20
|
+
"html" => @options.html,
|
|
21
|
+
"text" => @options.text
|
|
22
|
+
}.compact
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def default_headers
|
|
28
|
+
{"x-lettermint-token" => @api_key}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Email
|
|
5
|
+
module Providers
|
|
6
|
+
class Ses < Base
|
|
7
|
+
def self.config_options = %w[region access_key_id secret_access_key session_token]
|
|
8
|
+
|
|
9
|
+
def deliver
|
|
10
|
+
uri = URI.parse("https://email.#{@provider_options[:region]}.amazonaws.com/v2/email/outbound-emails")
|
|
11
|
+
|
|
12
|
+
request = Net::HTTP::Post.new(uri)
|
|
13
|
+
default_headers.merge(@custom_headers).each { |name, value| request[name] = value }
|
|
14
|
+
|
|
15
|
+
sign!(request)
|
|
16
|
+
|
|
17
|
+
request.body = JSON.dump(body)
|
|
18
|
+
|
|
19
|
+
options = {use_ssl: uri.scheme == "https"}
|
|
20
|
+
response = Net::HTTP.start(uri.hostname, uri.port, options) { it.request(request) }
|
|
21
|
+
|
|
22
|
+
Courrier::Email::Result.new(response: response)
|
|
23
|
+
rescue => error
|
|
24
|
+
Courrier::Email::Result.new(error: error)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def body
|
|
28
|
+
{
|
|
29
|
+
"FromEmailAddress" => @options.from,
|
|
30
|
+
"Destination" => {
|
|
31
|
+
"ToAddresses" => Array(@options.to),
|
|
32
|
+
"CcAddresses" => @options.cc ? Array(@options.cc) : nil,
|
|
33
|
+
"BccAddresses" => @options.bcc ? Array(@options.bcc) : nil
|
|
34
|
+
}.compact,
|
|
35
|
+
|
|
36
|
+
"ReplyToAddresses" => @options.reply_to ? Array(@options.reply_to) : nil,
|
|
37
|
+
"Content" => {
|
|
38
|
+
"Simple" => {
|
|
39
|
+
"Subject" => {"Data" => @options.subject},
|
|
40
|
+
|
|
41
|
+
"Body" => {
|
|
42
|
+
"Text" => {"Data" => @options.text},
|
|
43
|
+
"Html" => {"Data" => @options.html}
|
|
44
|
+
}.compact
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}.compact
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def default_headers
|
|
53
|
+
{
|
|
54
|
+
"Content-Type" => "application/json",
|
|
55
|
+
"Accept" => "application/json"
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def sign!(request)
|
|
60
|
+
require "aws-sigv4"
|
|
61
|
+
|
|
62
|
+
Aws::Sigv4::Signer.new(
|
|
63
|
+
service: "ses",
|
|
64
|
+
region: @provider_options[:region],
|
|
65
|
+
access_key_id: @provider_options[:access_key_id],
|
|
66
|
+
secret_access_key: @provider_options[:secret_access_key],
|
|
67
|
+
session_token: @provider_options[:session_token]
|
|
68
|
+
).sign_request(
|
|
69
|
+
http_method: request.method,
|
|
70
|
+
url: request.uri.to_s,
|
|
71
|
+
headers: request.to_hash
|
|
72
|
+
).headers.each { |name, value| request[name] = value }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Email
|
|
5
|
+
module Providers
|
|
6
|
+
class Smtp2go < Base
|
|
7
|
+
ENDPOINT_URL = "https://api.smtp2go.com/v3/email/send"
|
|
8
|
+
|
|
9
|
+
def body
|
|
10
|
+
{
|
|
11
|
+
"sender" => @options.from,
|
|
12
|
+
"to" => @options.to.to_s.split(",").map(&:strip),
|
|
13
|
+
"cc" => @options.cc&.split(",")&.map(&:strip),
|
|
14
|
+
"bcc" => @options.bcc&.split(",")&.map(&:strip),
|
|
15
|
+
"subject" => @options.subject,
|
|
16
|
+
"html_body" => @options.html,
|
|
17
|
+
"text_body" => @options.text
|
|
18
|
+
}.compact
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def default_headers
|
|
24
|
+
{"X-Smtp2go-Api-Key" => @api_key}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -25,7 +25,7 @@ module Courrier
|
|
|
25
25
|
options = {use_ssl: uri.scheme == "https"}
|
|
26
26
|
|
|
27
27
|
begin
|
|
28
|
-
response = Net::HTTP.start(uri.hostname, uri.port, options) {
|
|
28
|
+
response = Net::HTTP.start(uri.hostname, uri.port, options) { it.request(request) }
|
|
29
29
|
|
|
30
30
|
Result.new(response: response)
|
|
31
31
|
rescue => error
|
|
@@ -11,30 +11,30 @@ module Courrier
|
|
|
11
11
|
|
|
12
12
|
def to_text
|
|
13
13
|
Nokogiri::HTML(@content)
|
|
14
|
-
.then { remove_unwanted_elements(
|
|
15
|
-
.then { process_links(
|
|
16
|
-
.then { preserve_line_breaks(
|
|
17
|
-
.then { clean_up(
|
|
14
|
+
.then { remove_unwanted_elements(it) }
|
|
15
|
+
.then { process_links(it) }
|
|
16
|
+
.then { preserve_line_breaks(it) }
|
|
17
|
+
.then { clean_up(it) }
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
22
|
BLOCK_ELEMENTS = %w[p div h1 h2 h3 h4 h5 h6 pre blockquote li]
|
|
23
23
|
|
|
24
|
-
def remove_unwanted_elements(html) = html.tap {
|
|
24
|
+
def remove_unwanted_elements(html) = html.tap { it.css("script, style").remove }
|
|
25
25
|
|
|
26
26
|
def process_links(html)
|
|
27
27
|
html.tap do |document|
|
|
28
28
|
document.css("a")
|
|
29
|
-
.select { valid?(
|
|
30
|
-
.reject {
|
|
31
|
-
.each {
|
|
29
|
+
.select { valid?(it) }
|
|
30
|
+
.reject { it.text.strip.empty? || it.text.strip == it["href"] }
|
|
31
|
+
.each { it.content = "#{it.text.strip} (#{it["href"]})" }
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def preserve_line_breaks(html)
|
|
36
36
|
html.tap do |document|
|
|
37
|
-
document.css(BLOCK_ELEMENTS.join(",")).each {
|
|
37
|
+
document.css(BLOCK_ELEMENTS.join(",")).each { it.after("\n") }
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
data/lib/courrier/email.rb
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require "erb"
|
|
4
4
|
|
|
5
5
|
require "courrier/email/address"
|
|
6
|
-
require "courrier/jobs/email_delivery_job" if defined?(Rails)
|
|
7
6
|
require "courrier/email/layouts"
|
|
8
7
|
require "courrier/markdown"
|
|
9
8
|
require "courrier/email/options"
|
|
@@ -52,29 +51,41 @@ module Courrier
|
|
|
52
51
|
self.layouts = options
|
|
53
52
|
end
|
|
54
53
|
|
|
54
|
+
def before_deliver(&block)
|
|
55
|
+
(@before_deliver ||= []) << block
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def after_deliver(&block)
|
|
59
|
+
(@after_deliver ||= []) << block
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def before_deliver_callbacks
|
|
63
|
+
return @before_deliver if @before_deliver
|
|
64
|
+
return superclass.before_deliver_callbacks if superclass.respond_to?(:before_deliver_callbacks)
|
|
65
|
+
|
|
66
|
+
[]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def after_deliver_callbacks
|
|
70
|
+
return @after_deliver if @after_deliver
|
|
71
|
+
return superclass.after_deliver_callbacks if superclass.respond_to?(:after_deliver_callbacks)
|
|
72
|
+
|
|
73
|
+
[]
|
|
74
|
+
end
|
|
75
|
+
|
|
55
76
|
def deliver(**options)
|
|
56
77
|
new(options).deliver_now
|
|
57
78
|
end
|
|
58
79
|
alias_method :deliver_now, :deliver
|
|
59
80
|
|
|
60
|
-
def deliver_later(**options)
|
|
61
|
-
new(options).deliver_later
|
|
62
|
-
end
|
|
63
|
-
|
|
64
81
|
def inherited(subclass)
|
|
65
82
|
super
|
|
66
|
-
|
|
67
|
-
# If you read this and know how to move this Rails-specific logic somewhere
|
|
68
|
-
# else, e.g. `lib/courrier/railtie.rb`, open a PR ❤️
|
|
69
|
-
if defined?(Rails) && Rails.application
|
|
70
|
-
subclass.include Rails.application.routes.url_helpers
|
|
71
|
-
end
|
|
72
83
|
end
|
|
73
84
|
end
|
|
74
85
|
|
|
75
86
|
def initialize(options = {})
|
|
76
|
-
@provider = options[:provider] ||
|
|
77
|
-
@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)
|
|
78
89
|
|
|
79
90
|
@default_url_options = self.class.default_url_options.merge(options[:default_url_options] || {})
|
|
80
91
|
@context_options = options.except(:provider, :api_key, :from, :to, :reply_to, :cc, :bcc, :subject, :text, :html)
|
|
@@ -100,6 +111,8 @@ module Courrier
|
|
|
100
111
|
return nil
|
|
101
112
|
end
|
|
102
113
|
|
|
114
|
+
return nil if self.class.before_deliver_callbacks.any? { |callback| callback.call(self) == false }
|
|
115
|
+
|
|
103
116
|
Provider.new(
|
|
104
117
|
provider: @provider,
|
|
105
118
|
api_key: @api_key,
|
|
@@ -107,33 +120,13 @@ module Courrier
|
|
|
107
120
|
provider_options: Courrier.configuration&.providers&.[](@provider.to_s.downcase.to_sym),
|
|
108
121
|
context_options: @context_options,
|
|
109
122
|
custom_headers: self.class.headers
|
|
110
|
-
).deliver
|
|
111
|
-
|
|
112
|
-
alias_method :deliver_now, :deliver
|
|
123
|
+
).deliver.tap do |result|
|
|
124
|
+
Test.record(self, result)
|
|
113
125
|
|
|
114
|
-
|
|
115
|
-
if delivery_disabled?
|
|
116
|
-
Courrier.configuration&.logger&.info "[Courrier] Email delivery skipped: delivery is disabled via environment variable"
|
|
117
|
-
|
|
118
|
-
return nil
|
|
126
|
+
self.class.after_deliver_callbacks.each { |callback| callback.call(self, result) }
|
|
119
127
|
end
|
|
120
|
-
|
|
121
|
-
data = {
|
|
122
|
-
email_class: self.class.name,
|
|
123
|
-
provider: @provider,
|
|
124
|
-
api_key: @api_key,
|
|
125
|
-
options: @options.to_h,
|
|
126
|
-
provider_options: Courrier.configuration&.providers&.[](@provider.to_s.downcase.to_sym),
|
|
127
|
-
context_options: @context_options
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
job = Courrier::Jobs::EmailDeliveryJob
|
|
131
|
-
job = job.set(**self.class.queue_options) if self.class.queue_options.any?
|
|
132
|
-
|
|
133
|
-
job.perform_later(data)
|
|
134
|
-
rescue => error
|
|
135
|
-
raise Courrier::BackgroundDeliveryError, "Failed to enqueue email: #{error.message}"
|
|
136
128
|
end
|
|
129
|
+
alias_method :deliver_now, :deliver
|
|
137
130
|
|
|
138
131
|
private
|
|
139
132
|
|
data/lib/courrier/errors.rb
CHANGED
|
@@ -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
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
module Test
|
|
5
|
+
Delivery = Data.define(:email_class, :to, :from, :reply_to, :cc, :bcc, :subject, :body, :headers, :provider, :result, :timestamp) do
|
|
6
|
+
def success?
|
|
7
|
+
result.success?
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def deliveries
|
|
13
|
+
@deliveries ||= []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def clear!
|
|
17
|
+
@deliveries = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def record(email, result)
|
|
21
|
+
deliveries << Delivery.new(
|
|
22
|
+
email_class: email.class.name,
|
|
23
|
+
to: email.options.to,
|
|
24
|
+
from: email.options.from,
|
|
25
|
+
reply_to: email.options.reply_to,
|
|
26
|
+
cc: email.options.cc,
|
|
27
|
+
bcc: email.options.bcc,
|
|
28
|
+
subject: email.options.subject,
|
|
29
|
+
body: {text: email.options.text, html: email.options.html},
|
|
30
|
+
headers: email.class.headers,
|
|
31
|
+
provider: email.provider,
|
|
32
|
+
result: result,
|
|
33
|
+
timestamp: Time.now
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
module TestHelper
|
|
5
|
+
def assert_emails_delivered(count)
|
|
6
|
+
actual = Test.deliveries.size
|
|
7
|
+
|
|
8
|
+
assert_equal count, actual, "Expected #{count} email(s) to be delivered, but #{actual} were delivered"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def assert_no_emails_delivered
|
|
12
|
+
assert_emails_delivered(0)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def assert_email_delivered(email_class = nil, to: nil, from: nil, subject: nil, provider: nil)
|
|
16
|
+
deliveries = Test.deliveries
|
|
17
|
+
|
|
18
|
+
matching = deliveries.find do |delivery|
|
|
19
|
+
match_email_class(email_class, delivery.email_class) &&
|
|
20
|
+
match_recipient(to, delivery.to) &&
|
|
21
|
+
match_recipient(from, delivery.from) &&
|
|
22
|
+
match_subject(subject, delivery.subject) &&
|
|
23
|
+
match_provider(provider, delivery.provider)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
assert matching, assertion_message(email_class, to: to, from: from, subject: subject, provider: provider, deliveries: deliveries)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def match_email_class(expected, actual)
|
|
32
|
+
return true if expected.nil?
|
|
33
|
+
|
|
34
|
+
expected == actual || (expected.is_a?(Class) && actual == expected.name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def match_recipient(expected, actual)
|
|
38
|
+
return true if expected.nil?
|
|
39
|
+
|
|
40
|
+
actual.to_s.include?(expected.to_s)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def match_subject(expected, actual)
|
|
44
|
+
return true if expected.nil?
|
|
45
|
+
|
|
46
|
+
actual.to_s.include?(expected.to_s)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def match_provider(expected, actual)
|
|
50
|
+
return true if expected.nil?
|
|
51
|
+
|
|
52
|
+
actual.to_s == expected.to_s
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def assertion_message(email_class, to:, from:, subject:, provider:, deliveries:)
|
|
56
|
+
"Expected email matching #{[].tap do |criteria|
|
|
57
|
+
criteria << "email_class=#{email_class}" if email_class
|
|
58
|
+
criteria << "to=#{to}" if to
|
|
59
|
+
criteria << "from=#{from}" if from
|
|
60
|
+
criteria << "subject=#{subject}" if subject
|
|
61
|
+
criteria << "provider=#{provider}" if provider
|
|
62
|
+
end.join(", ")} but none found. #{deliveries.size} email(s) delivered."
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
data/lib/courrier/version.rb
CHANGED
data/lib/courrier.rb
CHANGED
|
@@ -5,8 +5,8 @@ require "courrier/errors"
|
|
|
5
5
|
require "courrier/configuration"
|
|
6
6
|
require "courrier/email"
|
|
7
7
|
require "courrier/subscriber"
|
|
8
|
-
require "courrier/
|
|
9
|
-
require "courrier/
|
|
8
|
+
require "courrier/test"
|
|
9
|
+
require "courrier/test_helper"
|
|
10
10
|
|
|
11
11
|
module Courrier
|
|
12
12
|
end
|