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
@@ -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