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
@@ -4,20 +4,24 @@ module SendGridMailer
4
4
 
5
5
  config.generators do |g|
6
6
  g.test_framework :rspec, fixture: false
7
- g.fixture_replacement :factory_girl, dir: "spec/factories"
7
+ g.fixture_replacement :factory_bot, dir: "spec/factories"
8
8
  end
9
9
 
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
 
18
- initializer "add_sendgrid_deliverer", before: "action_mailer.set_configs" do
20
+ initializer "add_sendgrid_deliverers", before: "action_mailer.set_configs" do
21
+ require_relative "./dev_deliverer"
19
22
  require_relative "./deliverer"
20
23
  ActionMailer::Base.add_delivery_method(:sendgrid, SendGridMailer::Deliverer)
24
+ ActionMailer::Base.add_delivery_method(:sendgrid_dev, SendGridMailer::DevDeliverer)
21
25
  end
22
26
  end
23
27
  end
@@ -1,2 +1,21 @@
1
- class SendGridMailer::Exception < ::Exception
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
+ error_message = errors.map { |err| err['message'] }.join('. ')
18
+ super("Sendgrid API error. Code: #{error_code}. Errors: #{error_message}")
19
+ end
20
+ end
2
21
  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,72 +1,75 @@
1
1
  module SendGridMailer
2
- class Logger
3
- attr_reader :definition
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 = data.keys.map do |k|
25
- d = data[k].to_s
26
- "#{k}: #{(d.blank? ? '-' : d)}"
27
- end.join("\n")
20
+ log(build_definition_message(data))
21
+ end
28
22
 
29
- Rails.logger.info("\n#{data}")
23
+ def log_api_success_response(status_code, api_call_type)
24
+ log("Succesfully called the SendGrid API :)\nStatus Code: #{status_code}")
30
25
  end
31
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
32
26
 
33
- def log_result(response)
34
- msg = "The E-mail was successfully sent :)\nStatus Code: #{response.status_code}"
27
+ def log_api_error_response(status_code, errors, api_call_type)
28
+ msg = "There was a problem calling the SendGrid API :(\nStatus Code: #{status_code}\nErrors:"
29
+ msg += log_errors(errors)
30
+ log(msg)
31
+ end
35
32
 
36
- if response.status_code != "202"
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
- private
40
+ def build_definition_message(data)
41
+ data = data.keys.map do |k|
42
+ d = data[k].to_s
43
+ "#{k}: #{d.presence || '-'}"
44
+ end.join("\n")
45
+ end
47
46
 
48
47
  def log_email(email)
49
48
  return if email.blank?
49
+
50
50
  email["email"]
51
51
  end
52
52
 
53
- def log_emails(origin)
53
+ def log_emails(personalization, origin)
54
54
  emails = personalization.send(origin)
55
55
  return if emails.blank?
56
+
56
57
  emails.map do |email|
57
58
  log_email(email)
58
59
  end.join(", ")
59
60
  end
60
61
 
61
- def log_attachments
62
+ def log_attachments(mail)
62
63
  return if mail.attachments.blank?
64
+
63
65
  mail.attachments.map do |f|
64
66
  "\n\t#{f['filename']}"
65
67
  end.join("")
66
68
  end
67
69
 
68
- def log_contents
70
+ def log_contents(mail)
69
71
  return if mail.contents.blank?
72
+
70
73
  mail.contents.map do |content|
71
74
  "\n\ttype: #{content['type']}\n\tvalue: #{content['value']}"
72
75
  end.join("")
@@ -74,17 +77,14 @@ module SendGridMailer
74
77
 
75
78
  def log_pairs(hash)
76
79
  return if hash.blank?
80
+
77
81
  hash.keys.map do |k|
78
82
  "\n\t#{k} => #{hash[k]}"
79
83
  end.join("")
80
84
  end
81
85
 
82
- def mail
83
- definition.mail
84
- end
85
-
86
- def log_errors(body)
87
- JSON.parse(body)["errors"].map do |error|
86
+ def log_errors(errors)
87
+ errors.map do |error|
88
88
  msg = []
89
89
  msg << "#{error['field']}: " if error['field']
90
90
  msg << error['message']
@@ -92,9 +92,5 @@ module SendGridMailer
92
92
  "\n\t* #{msg.join('')}"
93
93
  end.join("")
94
94
  end
95
-
96
- def personalization
97
- definition.personalization
98
- end
99
95
  end
100
96
  end
@@ -9,8 +9,7 @@ module ActionMailer
9
9
  end
10
10
 
11
11
  def mail(headers = {}, &_block)
12
- return old_mail(headers, &_block) if self.class.delivery_method != :sendgrid
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
- wrap_delivery_behavior!
28
- @_mail_was_called = true
29
- m
26
+ deliverer&.new&.deliver!(sg_definition)
30
27
  end
31
28
 
32
29
  private
@@ -60,11 +57,12 @@ 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?
62
+
66
63
  set_content(params[:body], params[:content_type])
67
64
  return if sg_definition.content?
65
+
68
66
  set_body_from_tpl(params)
69
67
  end
70
68
 
@@ -83,7 +81,20 @@ module ActionMailer
83
81
  end
84
82
 
85
83
  def sg_definition
86
- @_message.sg_definition
84
+ @sg_definition ||= SendGridMailer::Definition.new
85
+ end
86
+
87
+ def deliverer
88
+ case self.class.delivery_method
89
+ when :sendgrid_dev
90
+ SendGridMailer::DevDeliverer
91
+ when :sendgrid
92
+ SendGridMailer::Deliverer
93
+ end
94
+ end
95
+
96
+ def enabled_sendgrid?
97
+ [:sendgrid, :sendgrid_dev].include?(self.class.delivery_method)
87
98
  end
88
99
  end
89
100
  end
@@ -1,3 +1,3 @@
1
1
  module SendGridMailer
2
- VERSION = "0.5.0"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -19,13 +19,19 @@ Gem::Specification.new do |s|
19
19
  s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  s.test_files = Dir["spec/**/*"]
21
21
 
22
+ s.add_dependency "handlebars", "~> 0.8.0"
23
+ s.add_dependency "letter_opener", "~> 1.7.0"
22
24
  s.add_dependency "rails", ">= 4.2.0"
23
- s.add_dependency "sendgrid-ruby", "~> 4.0", ">= 4.0.4"
25
+ s.add_dependency "sendgrid-ruby", "~> 5", ">= 5.3.0"
26
+
27
+ s.add_development_dependency "coveralls"
28
+ s.add_development_dependency "factory_bot_rails"
29
+ s.add_development_dependency "guard-rspec", "~> 4.7"
24
30
  s.add_development_dependency "pry"
25
31
  s.add_development_dependency "pry-rails"
26
- s.add_development_dependency "sqlite3"
27
- s.add_development_dependency "rspec-rails", "~> 3.4.0"
28
- s.add_development_dependency "guard-rspec", "~> 4.7"
29
- s.add_development_dependency "factory_girl_rails", "~> 4.6.0"
30
- s.add_development_dependency "coveralls"
32
+ s.add_development_dependency "rspec-rails", "~> 3.5.0"
33
+ s.add_development_dependency "rspec_junit_formatter"
34
+ s.add_development_dependency "rubocop", "0.65.0"
35
+ s.add_development_dependency "rubocop-rspec"
36
+ s.add_development_dependency "sqlite3", "~> 1.4"
31
37
  end
data/spec/dummy/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  # Add your own tasks in files placed in lib/tasks ending in .rake,
2
2
  # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
3
 
4
- require File.expand_path('../config/application', __FILE__)
4
+ require_relative "config/application"
5
5
 
6
6
  Rails.application.load_tasks
@@ -0,0 +1,3 @@
1
+ //= link_tree ../images
2
+ //= link_directory ../stylesheets .css
3
+ //= link send_grid_mailer_manifest.js
@@ -6,9 +6,9 @@
6
6
  * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
7
  *
8
8
  * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
- * compiled file so the styles you add here take precedence over styles defined in any styles
10
- * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
- * file per style scope.
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
12
  *
13
13
  *= require_tree .
14
14
  *= require_self
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Channel < ActionCable::Channel::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Connection < ActionCable::Connection::Base
3
+ end
4
+ end
@@ -1,5 +1,2 @@
1
1
  class ApplicationController < ActionController::Base
2
- # Prevent CSRF attacks by raising an exception.
3
- # For APIs, you may want to use :null_session instead.
4
- protect_from_forgery with: :exception
5
2
  end
@@ -5,9 +5,11 @@
5
5
  // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
6
  //
7
7
  // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
- // compiled file.
8
+ // compiled file. JavaScript code in this file should be added after the last require_* statement.
9
9
  //
10
10
  // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
11
  // about supported directives.
12
12
  //
13
+ //= require rails-ujs
14
+ //= require activestorage
13
15
  //= require_tree .
@@ -0,0 +1,7 @@
1
+ class ApplicationJob < ActiveJob::Base
2
+ # Automatically retry jobs that encountered a deadlock
3
+ # retry_on ActiveRecord::Deadlocked
4
+
5
+ # Most jobs are safe to ignore if the underlying records are no longer available
6
+ # discard_on ActiveJob::DeserializationError
7
+ end
@@ -1,4 +1,4 @@
1
1
  class ApplicationMailer < ActionMailer::Base
2
- default from: "from@example.com"
2
+ default from: 'from@example.com'
3
3
  layout 'mailer'
4
4
  end
@@ -44,11 +44,6 @@ class TestMailer < ApplicationMailer
44
44
  mail
45
45
  end
46
46
 
47
- def template_name_email
48
- set_template_name("my template name")
49
- mail(body: "X")
50
- end
51
-
52
47
  def template_id_params_email
53
48
  mail(template_id: "XXX")
54
49
  end
@@ -83,4 +78,16 @@ class TestMailer < ApplicationMailer
83
78
  substitute "%key2%", "value2"
84
79
  mail(body: "X")
85
80
  end
81
+
82
+ def template_with_substitutions_email(value)
83
+ set_template_id("XXX")
84
+ substitute "%key%", value
85
+ mail(to: "r1@platan.us", body: "X")
86
+ end
87
+
88
+ def dynamic_template_email(value)
89
+ set_template_id("XXX")
90
+ dynamic_template_data(key: value)
91
+ mail(to: "r1@platan.us")
92
+ end
86
93
  end
@@ -0,0 +1,3 @@
1
+ class ApplicationRecord < ActiveRecord::Base
2
+ self.abstract_class = true
3
+ end