send_grid_mailer 0.5.0 → 2.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 +5 -5
- data/.circleci/config.yml +102 -0
- data/.circleci/setup-rubygems.sh +3 -0
- data/.rubocop.yml +31 -591
- data/.ruby-version +1 -1
- data/CHANGELOG.md +39 -0
- data/README.md +69 -8
- data/lib/send_grid_mailer/api.rb +42 -0
- data/lib/send_grid_mailer/definition.rb +35 -23
- data/lib/send_grid_mailer/deliverer.rb +12 -35
- data/lib/send_grid_mailer/dev_deliverer.rb +70 -0
- data/lib/send_grid_mailer/engine.rb +7 -3
- data/lib/send_grid_mailer/errors.rb +20 -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 +36 -40
- data/lib/send_grid_mailer/mailer_base_ext.rb +18 -7
- data/lib/send_grid_mailer/version.rb +1 -1
- data/send_grid_mailer.gemspec +12 -6
- data/spec/dummy/Rakefile +1 -1
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/assets/stylesheets/application.css +3 -3
- data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
- data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
- data/spec/dummy/app/controllers/application_controller.rb +0 -3
- data/spec/dummy/app/{assets/javascripts → javascript/packs}/application.js +3 -1
- data/spec/dummy/app/jobs/application_job.rb +7 -0
- data/spec/dummy/app/mailers/application_mailer.rb +1 -1
- data/spec/dummy/app/mailers/test_mailer.rb +12 -5
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +10 -9
- data/spec/dummy/bin/rails +3 -3
- data/spec/dummy/bin/rake +2 -2
- data/spec/dummy/bin/setup +18 -14
- data/spec/dummy/config.ru +3 -1
- data/spec/dummy/config/application.rb +12 -22
- data/spec/dummy/config/boot.rb +3 -3
- data/spec/dummy/config/cable.yml +10 -0
- data/spec/dummy/config/database.yml +2 -2
- data/spec/dummy/config/environment.rb +1 -1
- data/spec/dummy/config/environments/development.rb +48 -18
- data/spec/dummy/config/environments/production.rb +63 -22
- data/spec/dummy/config/environments/test.rb +29 -12
- data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/dummy/config/initializers/assets.rb +4 -3
- data/spec/dummy/config/initializers/backtrace_silencers.rb +4 -3
- data/spec/dummy/config/initializers/content_security_policy.rb +28 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +2 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +3 -1
- data/spec/dummy/config/initializers/permissions_policy.rb +11 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +2 -2
- data/spec/dummy/config/locales/en.yml +11 -1
- data/spec/dummy/config/puma.rb +43 -0
- data/spec/dummy/config/routes.rb +1 -54
- data/spec/dummy/config/storage.yml +34 -0
- data/spec/dummy/public/404.html +6 -6
- data/spec/dummy/public/422.html +6 -6
- data/spec/dummy/public/500.html +6 -6
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/spec/lib/send_grid_mailer/definition_spec.rb +52 -23
- data/spec/dummy/spec/mailers/test_mailer_spec.rb +558 -327
- data/spec/rails_helper.rb +5 -7
- metadata +127 -43
- data/.hound.yml +0 -4
- data/.travis.yml +0 -15
- data/lib/send_grid_mailer/mail_message_ext.rb +0 -7
- data/spec/dummy/bin/bundle +0 -3
- data/spec/dummy/config/initializers/session_store.rb +0 -3
- data/spec/dummy/config/secrets.yml +0 -22
- data/spec/dummy/db/schema.rb +0 -16
- data/spec/dummy/spec/support/test_helpers.rb +0 -5
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.7
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,45 @@
|
|
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
|
+
### v2.0.0
|
6
|
+
|
7
|
+
##### Changed
|
8
|
+
|
9
|
+
* Replace travis with circleci.
|
10
|
+
|
11
|
+
##### Removed
|
12
|
+
|
13
|
+
* Support for Ruby 2.5
|
14
|
+
|
15
|
+
### v1.2.1
|
16
|
+
|
17
|
+
##### Fixed
|
18
|
+
|
19
|
+
* :sendgrid_dev delivery method now works with Rails templates.
|
20
|
+
|
21
|
+
### v1.2.0
|
22
|
+
|
23
|
+
##### Added
|
24
|
+
|
25
|
+
* Add :sendgrid_dev delivery method
|
26
|
+
|
27
|
+
### v1.1.0
|
28
|
+
|
29
|
+
##### Added
|
30
|
+
|
31
|
+
* Detailed API errors
|
32
|
+
|
33
|
+
### v1.0.0
|
34
|
+
|
35
|
+
##### Added
|
36
|
+
|
37
|
+
* Implement "a version" of Recipients Interceptor https://github.com/croaky/recipient_interceptor compatible with SendGrid.
|
38
|
+
* Raise exceptions when api fails.
|
39
|
+
|
40
|
+
##### Removed
|
41
|
+
|
42
|
+
* Remove ability to set templates by name
|
43
|
+
|
5
44
|
### v0.5.0
|
6
45
|
|
7
46
|
##### Changed
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# SendGrid Mailer
|
2
2
|
[](https://badge.fury.io/rb/send_grid_mailer)
|
3
|
-
[](https://app.circleci.com/pipelines/github/platanus/send_grid_mailer)
|
4
4
|
[](https://coveralls.io/github/platanus/send_grid_mailer)
|
5
5
|
|
6
6
|
Is an Action Mailer adapter for using SendGrid in a Rails application and
|
@@ -18,7 +18,16 @@ gem "send_grid_mailer"
|
|
18
18
|
bundle install
|
19
19
|
```
|
20
20
|
|
21
|
-
|
21
|
+
We provide two delivery methods. For development environments, where sending the email is not required, you can use `:sendgrid_dev` to open it in the browser:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
config.action_mailer.delivery_method = :sendgrid_dev
|
25
|
+
config.action_mailer.sendgrid_dev_settings = {
|
26
|
+
api_key: "YOUR-SENDGRID-API-KEY"
|
27
|
+
}
|
28
|
+
```
|
29
|
+
|
30
|
+
Otherwise, you can use `:sendgrid` to actually send the email:
|
22
31
|
|
23
32
|
```ruby
|
24
33
|
config.action_mailer.delivery_method = :sendgrid
|
@@ -27,6 +36,7 @@ config.action_mailer.sendgrid_settings = {
|
|
27
36
|
}
|
28
37
|
```
|
29
38
|
|
39
|
+
|
30
40
|
## Usage
|
31
41
|
|
32
42
|
With this adapter you will be able to:
|
@@ -141,11 +151,6 @@ class TestMailer < ApplicationMailer
|
|
141
151
|
mail
|
142
152
|
end
|
143
153
|
|
144
|
-
def my_email # through template's name
|
145
|
-
set_template_name("my template name")
|
146
|
-
mail
|
147
|
-
end
|
148
|
-
|
149
154
|
def my_email # through mail method's params
|
150
155
|
mail(template_id: "XXX")
|
151
156
|
end
|
@@ -164,7 +169,53 @@ class TestMailer < ApplicationMailer
|
|
164
169
|
end
|
165
170
|
```
|
166
171
|
|
167
|
-
|
172
|
+
#### Set Dynamic Template Data
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
class TestMailer < ApplicationMailer
|
176
|
+
def my_email
|
177
|
+
dynamic_template_data({ key1: "value1", key2: "value2" })
|
178
|
+
mail
|
179
|
+
end
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
#### Add Category
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
class TestMailer < ApplicationMailer
|
187
|
+
def my_email
|
188
|
+
add_category("value")
|
189
|
+
mail
|
190
|
+
end
|
191
|
+
end
|
192
|
+
```
|
193
|
+
|
194
|
+
> Remember: you need to specify al least: `body`, `template_id` or a Rails template.
|
195
|
+
|
196
|
+
## Recipient Interceptor
|
197
|
+
|
198
|
+
This gem is compatible with [Recipient Interceptor gem](https://github.com/croaky/recipient_interceptor/tree/v0.1.2).
|
199
|
+
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.
|
200
|
+
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.
|
201
|
+
|
202
|
+
To make it work...
|
203
|
+
|
204
|
+
Add to your Gemfile:
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
gem "send_grid_mailer"
|
208
|
+
gem "recipient_interceptor"
|
209
|
+
```
|
210
|
+
|
211
|
+
In, for example, your `/my-project/config/environments/development.rb` file:
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
Mail.register_interceptor RecipientInterceptor.new(
|
215
|
+
ENV["EMAIL_RECIPIENTS"],
|
216
|
+
subject_prefix: '[STAGING]'
|
217
|
+
)
|
218
|
+
```
|
168
219
|
|
169
220
|
## Testing
|
170
221
|
|
@@ -176,6 +227,16 @@ bundle exec guard
|
|
176
227
|
|
177
228
|
You need to put **all your tests** in the `/send_grid_mailer/spec/dummy/spec/` directory.
|
178
229
|
|
230
|
+
## Publishing
|
231
|
+
|
232
|
+
On master/main branch...
|
233
|
+
|
234
|
+
1. Change `VERSION` in `lib/send_grid_mailer/version.rb`.
|
235
|
+
2. Change `Unreleased` title to current version in `CHANGELOG.md`.
|
236
|
+
3. Commit new release. For example: `Releasing v0.1.0`.
|
237
|
+
4. Create tag. For example: `git tag v0.1.0`.
|
238
|
+
5. Push tag. For example: `git push origin v0.1.0`.
|
239
|
+
|
179
240
|
## Contributing
|
180
241
|
|
181
242
|
1. Fork it
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module SendGridMailer
|
2
|
+
class Api
|
3
|
+
include Logger
|
4
|
+
|
5
|
+
def initialize(api_key)
|
6
|
+
@api_key = api_key || raise(SendGridMailer::InvalidApiKey)
|
7
|
+
end
|
8
|
+
|
9
|
+
def send_mail(sg_definition)
|
10
|
+
response = sg_api.client.mail._('send').post(request_body: sg_definition.to_json)
|
11
|
+
handle_response(response, :mail)
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_template(sg_definition)
|
15
|
+
response = sg_api.client.templates._(sg_definition.mail.template_id).get()
|
16
|
+
handle_response(response, :template)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def handle_response(response, api_call_type)
|
22
|
+
status_code = response.status_code.to_i
|
23
|
+
if status_code.between?(400, 600)
|
24
|
+
errors = response_errors(response)
|
25
|
+
log_api_error_response(status_code, errors, api_call_type)
|
26
|
+
raise SendGridMailer::ApiError.new(status_code, errors)
|
27
|
+
end
|
28
|
+
|
29
|
+
log_api_success_response(status_code, api_call_type)
|
30
|
+
response
|
31
|
+
end
|
32
|
+
|
33
|
+
def response_errors(response)
|
34
|
+
body = JSON.parse(response.body)
|
35
|
+
body["errors"] || [{ "message" => body["error"] }]
|
36
|
+
end
|
37
|
+
|
38
|
+
def sg_api
|
39
|
+
@sg_api ||= SendGrid::API.new(api_key: @api_key)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -2,36 +2,36 @@ module SendGridMailer
|
|
2
2
|
class Definition
|
3
3
|
METHODS = [
|
4
4
|
:substitute,
|
5
|
+
:dynamic_template_data,
|
5
6
|
:set_template_id,
|
6
|
-
:set_template_name,
|
7
7
|
:set_sender,
|
8
8
|
:set_recipients,
|
9
9
|
:set_subject,
|
10
10
|
:set_content,
|
11
11
|
:add_attachment,
|
12
|
-
:add_header
|
12
|
+
:add_header,
|
13
|
+
:add_category
|
13
14
|
]
|
14
15
|
|
15
|
-
attr_reader :template_name
|
16
|
-
|
17
16
|
def substitute(key, value, default = "")
|
18
|
-
personalization.
|
19
|
-
key: key, value: value.to_s || default
|
17
|
+
personalization.add_substitution(
|
18
|
+
SendGrid::Substitution.new(key: key, value: value.to_s || default)
|
20
19
|
)
|
21
20
|
end
|
22
21
|
|
23
|
-
def
|
24
|
-
|
25
|
-
mail.template_id = value
|
22
|
+
def dynamic_template_data(object)
|
23
|
+
personalization.add_dynamic_template_data(object)
|
26
24
|
end
|
27
25
|
|
28
|
-
def
|
26
|
+
def set_template_id(value)
|
29
27
|
return unless value
|
30
|
-
|
28
|
+
|
29
|
+
mail.template_id = value
|
31
30
|
end
|
32
31
|
|
33
32
|
def set_sender(email)
|
34
33
|
return unless email
|
34
|
+
|
35
35
|
matched_format = email.match(/<(.+)>/)
|
36
36
|
if matched_format
|
37
37
|
address = matched_format[1]
|
@@ -45,19 +45,22 @@ module SendGridMailer
|
|
45
45
|
def set_recipients(mode, *emails)
|
46
46
|
emails.flatten.each do |email|
|
47
47
|
next unless email
|
48
|
-
|
48
|
+
|
49
|
+
personalization.send("add_#{mode}", SendGrid::Email.new(email: email))
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
52
53
|
def set_subject(value)
|
53
54
|
return unless value
|
55
|
+
|
54
56
|
personalization.subject = value
|
55
57
|
end
|
56
58
|
|
57
59
|
def set_content(value, type = nil)
|
58
60
|
return unless value
|
59
|
-
|
60
|
-
|
61
|
+
|
62
|
+
type ||= "text/plain"
|
63
|
+
mail.add_content(SendGrid::Content.new(type: type, value: value))
|
61
64
|
end
|
62
65
|
|
63
66
|
def add_attachment(file, name, type, disposition = "inline", content_id = nil)
|
@@ -67,16 +70,23 @@ module SendGridMailer
|
|
67
70
|
attachment.filename = name
|
68
71
|
attachment.disposition = disposition
|
69
72
|
attachment.content_id = content_id
|
70
|
-
mail.
|
73
|
+
mail.add_attachment(attachment)
|
71
74
|
end
|
72
75
|
|
73
76
|
def add_header(key, value)
|
74
77
|
return if !key || !value
|
75
|
-
|
78
|
+
|
79
|
+
personalization.add_header(SendGrid::Header.new(key: key, value: value))
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_category(value)
|
83
|
+
return unless value
|
84
|
+
|
85
|
+
mail.add_category(SendGrid::Category.new(name: value))
|
76
86
|
end
|
77
87
|
|
78
88
|
def to_json
|
79
|
-
mail.
|
89
|
+
mail.add_personalization(personalization) if personalization?
|
80
90
|
mail.to_json
|
81
91
|
end
|
82
92
|
|
@@ -84,20 +94,22 @@ module SendGridMailer
|
|
84
94
|
@mail ||= SendGrid::Mail.new
|
85
95
|
end
|
86
96
|
|
97
|
+
def clean_recipients(mode)
|
98
|
+
personalization.instance_variable_set("@#{mode}s", [])
|
99
|
+
end
|
100
|
+
|
87
101
|
def personalization
|
88
102
|
@personalization ||= SendGrid::Personalization.new
|
89
103
|
end
|
90
104
|
|
91
105
|
def personalization?; !personalization.to_json.empty? end
|
92
106
|
|
93
|
-
def content?;
|
94
|
-
|
95
|
-
def sender?; !mail.from.blank? end
|
107
|
+
def content?; mail.contents.present? end
|
96
108
|
|
97
|
-
def
|
109
|
+
def sender?; mail.from.present? end
|
98
110
|
|
99
|
-
def
|
111
|
+
def subject?; personalization.subject.present? end
|
100
112
|
|
101
|
-
def
|
113
|
+
def template_id?; mail.template_id.present? end
|
102
114
|
end
|
103
115
|
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
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module SendGridMailer
|
2
|
+
class DevDeliverer
|
3
|
+
include InterceptorsHandler
|
4
|
+
include Logger
|
5
|
+
require "letter_opener"
|
6
|
+
require "handlebars"
|
7
|
+
|
8
|
+
def deliver!(sg_definition)
|
9
|
+
@sg_definition = sg_definition
|
10
|
+
execute_interceptors(@sg_definition)
|
11
|
+
log_definition(@sg_definition)
|
12
|
+
letter_opener_delivery_method.deliver!(mail)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def sg_api
|
18
|
+
@sg_api ||= Api.new(api_key)
|
19
|
+
end
|
20
|
+
|
21
|
+
def api_key
|
22
|
+
Rails.application.config.action_mailer.sendgrid_dev_settings[:api_key]
|
23
|
+
rescue
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def letter_opener_delivery_method
|
28
|
+
@letter_opener_delivery_method ||= LetterOpener::DeliveryMethod.new(location: dev_emails_location)
|
29
|
+
end
|
30
|
+
|
31
|
+
def dev_emails_location
|
32
|
+
Rails.application.config.action_mailer.sendgrid_dev_settings[:emails_location] || "/tmp/mails"
|
33
|
+
rescue
|
34
|
+
"/tmp/mails"
|
35
|
+
end
|
36
|
+
|
37
|
+
def parsed_template
|
38
|
+
template_response = sg_api.get_template(@sg_definition)
|
39
|
+
template_versions = JSON.parse(template_response.body)["versions"]
|
40
|
+
return if template_versions.blank?
|
41
|
+
|
42
|
+
template_active_version = template_versions.find { |version| version["active"] == 1 }
|
43
|
+
template_content = template_active_version["html_content"]
|
44
|
+
@sg_definition.personalization.substitutions.each { |k, v| template_content.gsub!(k, v) }
|
45
|
+
template = Handlebars::Context.new.compile(template_content)
|
46
|
+
template_content = template.call(@sg_definition.personalization.dynamic_template_data)
|
47
|
+
template_content
|
48
|
+
end
|
49
|
+
|
50
|
+
def emails(origin)
|
51
|
+
@emails ||= {}
|
52
|
+
return @emails[origin] if @emails.has_key?(origin)
|
53
|
+
|
54
|
+
@emails[origin] = @sg_definition.personalization.send(origin)&.map {|em| em["email"]}
|
55
|
+
end
|
56
|
+
|
57
|
+
def mail
|
58
|
+
template = (parsed_template || @sg_definition.mail.contents[0]['value']).html_safe
|
59
|
+
m = Mail.new
|
60
|
+
m.html_part = template
|
61
|
+
m.subject = @sg_definition.personalization.subject
|
62
|
+
m.from = @sg_definition.mail.from["email"] if @sg_definition.mail.from.present?
|
63
|
+
m.to = emails(:tos) if emails(:tos).present?
|
64
|
+
m.cc = emails(:ccs) if emails(:ccs).present?
|
65
|
+
m.bcc = emails(:bccs) if emails(:bccs).present?
|
66
|
+
|
67
|
+
m
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|