send_grid_mailer 0.5.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/CHANGELOG.md +11 -0
- data/README.md +25 -6
- data/lib/send_grid_mailer/api.rb +37 -0
- data/lib/send_grid_mailer/definition.rb +4 -10
- data/lib/send_grid_mailer/deliverer.rb +12 -35
- data/lib/send_grid_mailer/engine.rb +3 -1
- data/lib/send_grid_mailer/errors.rb +19 -1
- data/lib/send_grid_mailer/interceptor/recipient_interceptor.rb +42 -0
- data/lib/send_grid_mailer/interceptors_handler.rb +14 -0
- data/lib/send_grid_mailer/logger.rb +31 -40
- data/lib/send_grid_mailer/mailer_base_ext.rb +7 -7
- data/lib/send_grid_mailer/version.rb +1 -1
- data/spec/dummy/app/mailers/test_mailer.rb +0 -5
- data/spec/dummy/spec/mailers/test_mailer_spec.rb +413 -347
- metadata +5 -3
- data/lib/send_grid_mailer/mail_message_ext.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8269564f91417e0b4e8813c6be1db5f04be828a
|
4
|
+
data.tar.gz: b49dbc23ca7b63e7f8be14c8a47340bb0cda26ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 420118f02069b82f5a6779f481dfb32a0181e29e191b64c31f7afed86fcd4df8b361c7433128ce1504175b3459a47e1de84de119397e4c5bdfdf5d4848290161
|
7
|
+
data.tar.gz: 75f581ffef9bdba13de8fa9c4790ebd0fef055d2aef4f38f987966b73d962cb678e4f67dd2a7d52497faf2bff163143d8ed56a9ea4f727ae2c68f1ddb0e33efc
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,17 @@
|
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
4
4
|
|
5
|
+
### v1.0.0
|
6
|
+
|
7
|
+
##### Added
|
8
|
+
|
9
|
+
* Implement "a version" of Recipients Interceptor https://github.com/croaky/recipient_interceptor compatible with SendGrid.
|
10
|
+
* Raise exceptions when api fails.
|
11
|
+
|
12
|
+
##### Removed
|
13
|
+
|
14
|
+
* Remove ability to set templates by name
|
15
|
+
|
5
16
|
### v0.5.0
|
6
17
|
|
7
18
|
##### Changed
|
data/README.md
CHANGED
@@ -141,11 +141,6 @@ class TestMailer < ApplicationMailer
|
|
141
141
|
mail
|
142
142
|
end
|
143
143
|
|
144
|
-
def my_email # through template's name
|
145
|
-
set_template_name("my template name")
|
146
|
-
mail
|
147
|
-
end
|
148
|
-
|
149
144
|
def my_email # through mail method's params
|
150
145
|
mail(template_id: "XXX")
|
151
146
|
end
|
@@ -164,7 +159,31 @@ class TestMailer < ApplicationMailer
|
|
164
159
|
end
|
165
160
|
```
|
166
161
|
|
167
|
-
> Remember: you need to specify al least: `body`, `
|
162
|
+
> Remember: you need to specify al least: `body`, `template_id` or a Rails template.
|
163
|
+
|
164
|
+
## Recipient Interceptor
|
165
|
+
|
166
|
+
This gem is compatible with [Recipient Interceptor gem](https://github.com/croaky/recipient_interceptor/tree/v0.1.2).
|
167
|
+
However, this gem only uses its configuration. Internally, we modify the behaviour to play nice with [sengrid-ruby](https://github.com/sendgrid/sendgrid-ruby) gem.
|
168
|
+
So, the current code is based on [Recipient Interceptor v0.1.2](https://github.com/croaky/recipient_interceptor/tree/v0.1.2). New versions may not work.
|
169
|
+
|
170
|
+
To make it work...
|
171
|
+
|
172
|
+
Add to your Gemfile:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
gem "send_grid_mailer"
|
176
|
+
gem "recipient_interceptor"
|
177
|
+
```
|
178
|
+
|
179
|
+
In, for example, your `/my-project/config/environments/development.rb` file:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
Mail.register_interceptor RecipientInterceptor.new(
|
183
|
+
ENV["EMAIL_RECIPIENTS"],
|
184
|
+
subject_prefix: '[STAGING]'
|
185
|
+
)
|
186
|
+
```
|
168
187
|
|
169
188
|
## Testing
|
170
189
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module SendGridMailer
|
2
|
+
class Api
|
3
|
+
include Logger
|
4
|
+
|
5
|
+
SUCCESS_CODE = 202
|
6
|
+
|
7
|
+
def initialize(api_key)
|
8
|
+
@api_key = api_key || raise(SendGridMailer::InvalidApiKey)
|
9
|
+
end
|
10
|
+
|
11
|
+
def send_mail(sg_definition)
|
12
|
+
response = sg_api.client.mail._('send').post(request_body: sg_definition.to_json)
|
13
|
+
handle_response(response)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def handle_response(response)
|
19
|
+
if response.status_code.to_i != SUCCESS_CODE
|
20
|
+
errors = response_errors(response)
|
21
|
+
log_api_error_response(response.status_code, errors)
|
22
|
+
raise SendGridMailer::ApiError.new(response.status_code, errors)
|
23
|
+
end
|
24
|
+
|
25
|
+
log_api_success_response(response)
|
26
|
+
response
|
27
|
+
end
|
28
|
+
|
29
|
+
def response_errors(response)
|
30
|
+
JSON.parse(response.body)["errors"]
|
31
|
+
end
|
32
|
+
|
33
|
+
def sg_api
|
34
|
+
@sg_api ||= SendGrid::API.new(api_key: @api_key)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -3,7 +3,6 @@ module SendGridMailer
|
|
3
3
|
METHODS = [
|
4
4
|
:substitute,
|
5
5
|
:set_template_id,
|
6
|
-
:set_template_name,
|
7
6
|
:set_sender,
|
8
7
|
:set_recipients,
|
9
8
|
:set_subject,
|
@@ -12,8 +11,6 @@ module SendGridMailer
|
|
12
11
|
:add_header
|
13
12
|
]
|
14
13
|
|
15
|
-
attr_reader :template_name
|
16
|
-
|
17
14
|
def substitute(key, value, default = "")
|
18
15
|
personalization.substitutions = SendGrid::Substitution.new(
|
19
16
|
key: key, value: value.to_s || default
|
@@ -25,11 +22,6 @@ module SendGridMailer
|
|
25
22
|
mail.template_id = value
|
26
23
|
end
|
27
24
|
|
28
|
-
def set_template_name(value)
|
29
|
-
return unless value
|
30
|
-
@template_name = value
|
31
|
-
end
|
32
|
-
|
33
25
|
def set_sender(email)
|
34
26
|
return unless email
|
35
27
|
matched_format = email.match(/<(.+)>/)
|
@@ -84,6 +76,10 @@ module SendGridMailer
|
|
84
76
|
@mail ||= SendGrid::Mail.new
|
85
77
|
end
|
86
78
|
|
79
|
+
def clean_recipients(mode)
|
80
|
+
personalization.instance_variable_set("@#{mode}s", nil)
|
81
|
+
end
|
82
|
+
|
87
83
|
def personalization
|
88
84
|
@personalization ||= SendGrid::Personalization.new
|
89
85
|
end
|
@@ -97,7 +93,5 @@ module SendGridMailer
|
|
97
93
|
def subject?; !personalization.subject.blank? end
|
98
94
|
|
99
95
|
def template_id?; !mail.template_id.blank? end
|
100
|
-
|
101
|
-
def template_name?; !template_name.blank? end
|
102
96
|
end
|
103
97
|
end
|
@@ -1,47 +1,24 @@
|
|
1
1
|
module SendGridMailer
|
2
2
|
class Deliverer
|
3
|
-
|
3
|
+
include InterceptorsHandler
|
4
|
+
include Logger
|
4
5
|
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
def api_key
|
10
|
-
settings[:api_key] || raise(SendGridMailer::Exception.new("Missing sendgrid API key"))
|
11
|
-
end
|
12
|
-
|
13
|
-
def deliver!(msg)
|
14
|
-
set_template_id_from_name(msg.sg_definition)
|
15
|
-
logger = SendGridMailer::Logger.new(msg.sg_definition)
|
16
|
-
logger.log_definition
|
17
|
-
response = sg_api.client.mail._('send').post(request_body: msg.sg_definition.to_json)
|
18
|
-
logger.log_result(response)
|
19
|
-
response
|
6
|
+
def deliver!(sg_definition)
|
7
|
+
execute_interceptors(sg_definition)
|
8
|
+
log_definition(sg_definition)
|
9
|
+
sg_api.send_mail(sg_definition)
|
20
10
|
end
|
21
11
|
|
22
12
|
private
|
23
13
|
|
24
|
-
def
|
25
|
-
|
26
|
-
response = sg_api.client.templates.get
|
27
|
-
|
28
|
-
if response.status_code != "200"
|
29
|
-
m = "Error trying to get templates. Status Code: #{response.status_code}"
|
30
|
-
raise SendGridMailer::Exception.new(m)
|
31
|
-
end
|
32
|
-
|
33
|
-
JSON.parse(response.body)["templates"].each do |tpl|
|
34
|
-
definition.set_template_id(tpl["id"]) if tpl["name"] == definition.template_name
|
35
|
-
end
|
36
|
-
|
37
|
-
if !definition.template_id?
|
38
|
-
m = "No template with name #{definition.template_name}"
|
39
|
-
raise SendGridMailer::Exception.new(m)
|
40
|
-
end
|
14
|
+
def sg_api
|
15
|
+
@sg_api ||= Api.new(api_key)
|
41
16
|
end
|
42
17
|
|
43
|
-
def
|
44
|
-
|
18
|
+
def api_key
|
19
|
+
Rails.application.config.action_mailer.sendgrid_settings[:api_key]
|
20
|
+
rescue
|
21
|
+
nil
|
45
22
|
end
|
46
23
|
end
|
47
24
|
end
|
@@ -10,8 +10,10 @@ module SendGridMailer
|
|
10
10
|
initializer "initialize" do
|
11
11
|
require_relative "./errors"
|
12
12
|
require_relative "./logger"
|
13
|
+
require_relative "./api"
|
14
|
+
require_relative "./interceptors_handler"
|
15
|
+
require_relative "./interceptor/recipient_interceptor"
|
13
16
|
require_relative "./definition"
|
14
|
-
require_relative "./mail_message_ext"
|
15
17
|
require_relative "./mailer_base_ext"
|
16
18
|
end
|
17
19
|
|
@@ -1,2 +1,20 @@
|
|
1
|
-
|
1
|
+
module SendGridMailer
|
2
|
+
class Error < RuntimeError
|
3
|
+
end
|
4
|
+
|
5
|
+
class InvalidApiKey < Error
|
6
|
+
def initialize
|
7
|
+
super("the SendGrid API key is invalid or missing")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class ApiError < Error
|
12
|
+
attr_reader :error_code, :errors
|
13
|
+
|
14
|
+
def initialize(error_code, errors)
|
15
|
+
@error_code = error_code
|
16
|
+
@errors = errors
|
17
|
+
super("sendgrid api error")
|
18
|
+
end
|
19
|
+
end
|
2
20
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module SendGridMailer
|
2
|
+
module Interceptor
|
3
|
+
module RecipientInterceptor
|
4
|
+
def self.perform(sg_definition, interceptor)
|
5
|
+
add_custom_headers(sg_definition)
|
6
|
+
add_recipients(sg_definition, interceptor)
|
7
|
+
add_subject_prefix(sg_definition, interceptor)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.exec_recipients_interceptor(sg_definition, interceptor)
|
11
|
+
add_custom_headers(sg_definition)
|
12
|
+
add_recipients(sg_definition, interceptor)
|
13
|
+
add_subject_prefix(sg_definition, interceptor)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.add_subject_prefix(sg_definition, interceptor)
|
17
|
+
subject_prefix = interceptor.instance_variable_get(:@subject_prefix)
|
18
|
+
subject = [subject_prefix, sg_definition.personalization.subject].join(" ").strip
|
19
|
+
sg_definition.set_subject(subject)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.add_recipients(sg_definition, interceptor)
|
23
|
+
recipients = interceptor.instance_variable_get(:@recipients)
|
24
|
+
sg_definition.clean_recipients(:to)
|
25
|
+
sg_definition.clean_recipients(:cc)
|
26
|
+
sg_definition.clean_recipients(:bcc)
|
27
|
+
sg_definition.set_recipients(:to, recipients)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.add_custom_headers(sg_definition)
|
31
|
+
{
|
32
|
+
'X-Intercepted-To' => sg_definition.personalization.tos || [],
|
33
|
+
'X-Intercepted-Cc' => sg_definition.personalization.ccs || [],
|
34
|
+
'X-Intercepted-Bcc' => sg_definition.personalization.bccs || []
|
35
|
+
}.each do |header, addresses|
|
36
|
+
addresses_str = addresses.map { |a| a["email"] }.join(", ")
|
37
|
+
sg_definition.add_header(header, addresses_str)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module SendGridMailer
|
2
|
+
module InterceptorsHandler
|
3
|
+
def execute_interceptors(sg_definition)
|
4
|
+
registered_interceptors.each do |interceptor|
|
5
|
+
interceptor_class = "SendGridMailer::Interceptor::#{interceptor.class}".constantize
|
6
|
+
interceptor_class.perform(sg_definition, interceptor)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def registered_interceptors
|
11
|
+
::Mail.class_variable_get(:@@delivery_interceptors)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -1,56 +1,55 @@
|
|
1
1
|
module SendGridMailer
|
2
|
-
|
3
|
-
|
2
|
+
module Logger
|
3
|
+
def log_definition(definition)
|
4
|
+
mail = definition.mail
|
5
|
+
personalization = definition.personalization
|
4
6
|
|
5
|
-
def initialize(definition)
|
6
|
-
@definition = definition
|
7
|
-
end
|
8
|
-
|
9
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
10
|
-
def log_definition
|
11
7
|
data = {
|
12
8
|
"Subject" => personalization.subject,
|
13
9
|
"Template ID" => mail.template_id,
|
14
10
|
"From" => log_email(mail.from),
|
15
|
-
"To" => log_emails(:tos),
|
16
|
-
"Cc" => log_emails(:ccs),
|
17
|
-
"Bcc" => log_emails(:bccs),
|
11
|
+
"To" => log_emails(personalization, :tos),
|
12
|
+
"Cc" => log_emails(personalization, :ccs),
|
13
|
+
"Bcc" => log_emails(personalization, :bccs),
|
18
14
|
"Substitutions" => log_pairs(personalization.substitutions),
|
19
15
|
"Headers" => log_pairs(personalization.headers),
|
20
|
-
"body" => log_contents,
|
21
|
-
"Attachments" => log_attachments
|
16
|
+
"body" => log_contents(mail),
|
17
|
+
"Attachments" => log_attachments(mail)
|
22
18
|
}
|
23
19
|
|
24
|
-
data
|
25
|
-
|
26
|
-
"#{k}: #{(d.blank? ? '-' : d)}"
|
27
|
-
end.join("\n")
|
20
|
+
log(build_definition_message(data))
|
21
|
+
end
|
28
22
|
|
29
|
-
|
23
|
+
def log_api_success_response(response)
|
24
|
+
log("The E-mail was successfully sent :)\nStatus Code: #{response.status_code}")
|
30
25
|
end
|
31
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
32
26
|
|
33
|
-
def
|
34
|
-
msg = "The E-mail was
|
27
|
+
def log_api_error_response(status_code, errors)
|
28
|
+
msg = "The E-mail was not sent :(\nStatus Code: #{status_code}\nErrors:"
|
29
|
+
msg += log_errors(errors)
|
30
|
+
log(msg)
|
31
|
+
end
|
35
32
|
|
36
|
-
|
37
|
-
msg = "The E-mail was not sent :(\nStatus Code: #{response.status_code}\nErrors:"
|
38
|
-
msg += log_errors(response.body)
|
39
|
-
msg = msg
|
40
|
-
end
|
33
|
+
private
|
41
34
|
|
35
|
+
def log(msg)
|
42
36
|
Rails.logger.info("\n#{msg}")
|
43
37
|
nil
|
44
38
|
end
|
45
39
|
|
46
|
-
|
40
|
+
def build_definition_message(data)
|
41
|
+
data = data.keys.map do |k|
|
42
|
+
d = data[k].to_s
|
43
|
+
"#{k}: #{(d.blank? ? '-' : d)}"
|
44
|
+
end.join("\n")
|
45
|
+
end
|
47
46
|
|
48
47
|
def log_email(email)
|
49
48
|
return if email.blank?
|
50
49
|
email["email"]
|
51
50
|
end
|
52
51
|
|
53
|
-
def log_emails(origin)
|
52
|
+
def log_emails(personalization, origin)
|
54
53
|
emails = personalization.send(origin)
|
55
54
|
return if emails.blank?
|
56
55
|
emails.map do |email|
|
@@ -58,14 +57,14 @@ module SendGridMailer
|
|
58
57
|
end.join(", ")
|
59
58
|
end
|
60
59
|
|
61
|
-
def log_attachments
|
60
|
+
def log_attachments(mail)
|
62
61
|
return if mail.attachments.blank?
|
63
62
|
mail.attachments.map do |f|
|
64
63
|
"\n\t#{f['filename']}"
|
65
64
|
end.join("")
|
66
65
|
end
|
67
66
|
|
68
|
-
def log_contents
|
67
|
+
def log_contents(mail)
|
69
68
|
return if mail.contents.blank?
|
70
69
|
mail.contents.map do |content|
|
71
70
|
"\n\ttype: #{content['type']}\n\tvalue: #{content['value']}"
|
@@ -79,12 +78,8 @@ module SendGridMailer
|
|
79
78
|
end.join("")
|
80
79
|
end
|
81
80
|
|
82
|
-
def
|
83
|
-
|
84
|
-
end
|
85
|
-
|
86
|
-
def log_errors(body)
|
87
|
-
JSON.parse(body)["errors"].map do |error|
|
81
|
+
def log_errors(errors)
|
82
|
+
errors.map do |error|
|
88
83
|
msg = []
|
89
84
|
msg << "#{error['field']}: " if error['field']
|
90
85
|
msg << error['message']
|
@@ -92,9 +87,5 @@ module SendGridMailer
|
|
92
87
|
"\n\t* #{msg.join('')}"
|
93
88
|
end.join("")
|
94
89
|
end
|
95
|
-
|
96
|
-
def personalization
|
97
|
-
definition.personalization
|
98
|
-
end
|
99
90
|
end
|
100
91
|
end
|
@@ -9,8 +9,7 @@ module ActionMailer
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def mail(headers = {}, &_block)
|
12
|
-
return old_mail(headers, &_block)
|
13
|
-
m = @_message
|
12
|
+
return old_mail(headers, &_block) unless enabled_sendgrid?
|
14
13
|
|
15
14
|
# Call all the procs (if any)
|
16
15
|
default_values = {}
|
@@ -24,9 +23,7 @@ module ActionMailer
|
|
24
23
|
|
25
24
|
define_sg_mail(headers)
|
26
25
|
|
27
|
-
|
28
|
-
@_mail_was_called = true
|
29
|
-
m
|
26
|
+
SendGridMailer::Deliverer.new.deliver!(sg_definition)
|
30
27
|
end
|
31
28
|
|
32
29
|
private
|
@@ -60,7 +57,6 @@ module ActionMailer
|
|
60
57
|
end
|
61
58
|
|
62
59
|
def set_body(params)
|
63
|
-
return if sg_definition.template_name?
|
64
60
|
set_template_id(params[:template_id])
|
65
61
|
return if sg_definition.template_id?
|
66
62
|
set_content(params[:body], params[:content_type])
|
@@ -83,7 +79,11 @@ module ActionMailer
|
|
83
79
|
end
|
84
80
|
|
85
81
|
def sg_definition
|
86
|
-
@
|
82
|
+
@sg_definition ||= SendGridMailer::Definition.new
|
83
|
+
end
|
84
|
+
|
85
|
+
def enabled_sendgrid?
|
86
|
+
self.class.delivery_method == :sendgrid
|
87
87
|
end
|
88
88
|
end
|
89
89
|
end
|