send_grid_mailer 0.5.0 → 2.0.0

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