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.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +102 -0
  3. data/.circleci/setup-rubygems.sh +3 -0
  4. data/.rubocop.yml +31 -591
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +39 -0
  7. data/README.md +69 -8
  8. data/lib/send_grid_mailer/api.rb +42 -0
  9. data/lib/send_grid_mailer/definition.rb +35 -23
  10. data/lib/send_grid_mailer/deliverer.rb +12 -35
  11. data/lib/send_grid_mailer/dev_deliverer.rb +70 -0
  12. data/lib/send_grid_mailer/engine.rb +7 -3
  13. data/lib/send_grid_mailer/errors.rb +20 -1
  14. data/lib/send_grid_mailer/interceptor/recipient_interceptor.rb +42 -0
  15. data/lib/send_grid_mailer/interceptors_handler.rb +14 -0
  16. data/lib/send_grid_mailer/logger.rb +36 -40
  17. data/lib/send_grid_mailer/mailer_base_ext.rb +18 -7
  18. data/lib/send_grid_mailer/version.rb +1 -1
  19. data/send_grid_mailer.gemspec +12 -6
  20. data/spec/dummy/Rakefile +1 -1
  21. data/spec/dummy/app/assets/config/manifest.js +3 -0
  22. data/spec/dummy/app/assets/stylesheets/application.css +3 -3
  23. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  24. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  25. data/spec/dummy/app/controllers/application_controller.rb +0 -3
  26. data/spec/dummy/app/{assets/javascripts → javascript/packs}/application.js +3 -1
  27. data/spec/dummy/app/jobs/application_job.rb +7 -0
  28. data/spec/dummy/app/mailers/application_mailer.rb +1 -1
  29. data/spec/dummy/app/mailers/test_mailer.rb +12 -5
  30. data/spec/dummy/app/models/application_record.rb +3 -0
  31. data/spec/dummy/app/views/layouts/application.html.erb +10 -9
  32. data/spec/dummy/bin/rails +3 -3
  33. data/spec/dummy/bin/rake +2 -2
  34. data/spec/dummy/bin/setup +18 -14
  35. data/spec/dummy/config.ru +3 -1
  36. data/spec/dummy/config/application.rb +12 -22
  37. data/spec/dummy/config/boot.rb +3 -3
  38. data/spec/dummy/config/cable.yml +10 -0
  39. data/spec/dummy/config/database.yml +2 -2
  40. data/spec/dummy/config/environment.rb +1 -1
  41. data/spec/dummy/config/environments/development.rb +48 -18
  42. data/spec/dummy/config/environments/production.rb +63 -22
  43. data/spec/dummy/config/environments/test.rb +29 -12
  44. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  45. data/spec/dummy/config/initializers/assets.rb +4 -3
  46. data/spec/dummy/config/initializers/backtrace_silencers.rb +4 -3
  47. data/spec/dummy/config/initializers/content_security_policy.rb +28 -0
  48. data/spec/dummy/config/initializers/cookies_serializer.rb +2 -0
  49. data/spec/dummy/config/initializers/filter_parameter_logging.rb +3 -1
  50. data/spec/dummy/config/initializers/permissions_policy.rb +11 -0
  51. data/spec/dummy/config/initializers/wrap_parameters.rb +2 -2
  52. data/spec/dummy/config/locales/en.yml +11 -1
  53. data/spec/dummy/config/puma.rb +43 -0
  54. data/spec/dummy/config/routes.rb +1 -54
  55. data/spec/dummy/config/storage.yml +34 -0
  56. data/spec/dummy/public/404.html +6 -6
  57. data/spec/dummy/public/422.html +6 -6
  58. data/spec/dummy/public/500.html +6 -6
  59. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  60. data/spec/dummy/public/apple-touch-icon.png +0 -0
  61. data/spec/dummy/spec/lib/send_grid_mailer/definition_spec.rb +52 -23
  62. data/spec/dummy/spec/mailers/test_mailer_spec.rb +558 -327
  63. data/spec/rails_helper.rb +5 -7
  64. metadata +127 -43
  65. data/.hound.yml +0 -4
  66. data/.travis.yml +0 -15
  67. data/lib/send_grid_mailer/mail_message_ext.rb +0 -7
  68. data/spec/dummy/bin/bundle +0 -3
  69. data/spec/dummy/config/initializers/session_store.rb +0 -3
  70. data/spec/dummy/config/secrets.yml +0 -22
  71. data/spec/dummy/db/schema.rb +0 -16
  72. data/spec/dummy/spec/support/test_helpers.rb +0 -5
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.3
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
  [![Gem Version](https://badge.fury.io/rb/send_grid_mailer.svg)](https://badge.fury.io/rb/send_grid_mailer)
3
- [![Build Status](https://travis-ci.org/platanus/send_grid_mailer.svg?branch=master)](https://travis-ci.org/platanus/send_grid_mailer)
3
+ [![CircleCI](https://circleci.com/gh/platanus/send_grid_mailer.svg?style=shield)](https://app.circleci.com/pipelines/github/platanus/send_grid_mailer)
4
4
  [![Coverage Status](https://coveralls.io/repos/github/platanus/send_grid_mailer/badge.svg)](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
- In your environment file you need to add:
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
- > Remember: you need to specify al least: `body`, `template`, `template name` or a Rails template.
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.substitutions = SendGrid::Substitution.new(
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 set_template_id(value)
24
- return unless value
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 set_template_name(value)
26
+ def set_template_id(value)
29
27
  return unless value
30
- @template_name = value
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
- personalization.send("#{mode}=", SendGrid::Email.new(email: email))
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
- type = "text/plain" unless type
60
- mail.contents = SendGrid::Content.new(type: type, value: value)
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.attachments = attachment
73
+ mail.add_attachment(attachment)
71
74
  end
72
75
 
73
76
  def add_header(key, value)
74
77
  return if !key || !value
75
- personalization.headers = SendGrid::Header.new(key: key, value: value)
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.personalizations = personalization if personalization?
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?; !mail.contents.blank? end
94
-
95
- def sender?; !mail.from.blank? end
107
+ def content?; mail.contents.present? end
96
108
 
97
- def subject?; !personalization.subject.blank? end
109
+ def sender?; mail.from.present? end
98
110
 
99
- def template_id?; !mail.template_id.blank? end
111
+ def subject?; personalization.subject.present? end
100
112
 
101
- def template_name?; !template_name.blank? end
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
- attr_accessor :settings
3
+ include InterceptorsHandler
4
+ include Logger
4
5
 
5
- def initialize(settings)
6
- self.settings = settings.merge(return_response: true)
7
- end
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 set_template_id_from_name(definition)
25
- return unless definition.template_name
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 sg_api
44
- @sg_api ||= SendGrid::API.new(api_key: api_key)
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