send_grid_mailer 0.5.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|