config: simplify mailer configuration again
Move everything to initializers, and replace the email settings interceptor by a BalancerDeliveryMethod. It has the advantage that it can be configured entirely from the `config/environment.rb` file, without an extra file to look at.
This commit is contained in:
parent
27b42fe8ae
commit
847abca122
11 changed files with 205 additions and 109 deletions
38
app/lib/balancer_delivery_method.rb
Normal file
38
app/lib/balancer_delivery_method.rb
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# A Mail delivery method that randomly balances the actual delivery between different
|
||||||
|
# methods.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
#
|
||||||
|
# ```ruby
|
||||||
|
# ActionMailer::Base.add_delivery_method :balancer, BalancerDeliveryMethod
|
||||||
|
# config.action_mailer.balancer_settings = {
|
||||||
|
# smtp: 25,
|
||||||
|
# sendmail: 75
|
||||||
|
# }
|
||||||
|
# config.action_mailer.delivery_method = :balancer
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# Be sure to restart your server when you modify this file.
|
||||||
|
class BalancerDeliveryMethod
|
||||||
|
# Allows configuring the random number generator used for selecting a delivery method,
|
||||||
|
# mostly for testing purposes.
|
||||||
|
mattr_accessor :random, default: Random.new
|
||||||
|
|
||||||
|
def initialize(settings)
|
||||||
|
@delivery_methods = settings
|
||||||
|
end
|
||||||
|
|
||||||
|
def deliver!(mail)
|
||||||
|
balanced_delivery_method = delivery_method(mail)
|
||||||
|
ApplicationMailer.wrap_delivery_behavior(mail, balanced_delivery_method)
|
||||||
|
mail.deliver
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def delivery_method(mail)
|
||||||
|
@delivery_methods
|
||||||
|
.flat_map { |delivery_method, weight| [delivery_method] * weight }
|
||||||
|
.sample(random: self.class.random)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,4 +0,0 @@
|
||||||
module Mailtrap
|
|
||||||
class Smtp < ::Mail::SMTP
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,4 +0,0 @@
|
||||||
module Sendinblue
|
|
||||||
class Smtp < ::Mail::SMTP
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -75,40 +75,13 @@ Rails.application.configure do
|
||||||
config.assets.raise_runtime_errors = true
|
config.assets.raise_runtime_errors = true
|
||||||
|
|
||||||
# Action Mailer settings
|
# Action Mailer settings
|
||||||
|
config.action_mailer.delivery_method = :letter_opener
|
||||||
if ENV['SENDINBLUE_ENABLED'] == 'enabled'
|
|
||||||
config.action_mailer.delivery_method = :smtp
|
|
||||||
config.action_mailer.smtp_settings = {
|
|
||||||
user_name: Rails.application.secrets.sendinblue[:username],
|
|
||||||
password: Rails.application.secrets.sendinblue[:smtp_key],
|
|
||||||
address: 'smtp-relay.sendinblue.com',
|
|
||||||
domain: 'smtp-relay.sendinblue.com',
|
|
||||||
port: '587',
|
|
||||||
authentication: :cram_md5
|
|
||||||
}
|
|
||||||
else
|
|
||||||
# https://usehelo.com
|
|
||||||
if ENV['HELO_ENABLED'] == 'enabled'
|
|
||||||
config.action_mailer.delivery_method = :smtp
|
|
||||||
config.action_mailer.smtp_settings = {
|
|
||||||
user_name: 'demarches-simplifiees',
|
|
||||||
password: '',
|
|
||||||
address: '127.0.0.1',
|
|
||||||
domain: '127.0.0.1',
|
|
||||||
port: ENV.fetch('HELO_PORT', '2525'),
|
|
||||||
authentication: :plain
|
|
||||||
}
|
|
||||||
else
|
|
||||||
config.action_mailer.delivery_method = :letter_opener_web
|
|
||||||
end
|
|
||||||
|
|
||||||
config.action_mailer.default_url_options = {
|
config.action_mailer.default_url_options = {
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: 3000
|
port: 3000
|
||||||
}
|
}
|
||||||
|
|
||||||
config.action_mailer.asset_host = "http://" + ENV['APP_HOST']
|
config.action_mailer.asset_host = "http://" + ENV['APP_HOST']
|
||||||
end
|
|
||||||
|
|
||||||
Rails.application.routes.default_url_options = {
|
Rails.application.routes.default_url_options = {
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
require "active_support/core_ext/integer/time"
|
require "active_support/core_ext/integer/time"
|
||||||
require Rails.root.join("app/lib/mailtrap/smtp")
|
require Rails.root.join("app/lib/balancer_delivery_method")
|
||||||
require Rails.root.join("app/lib/sendinblue/smtp")
|
|
||||||
|
|
||||||
Rails.application.configure do
|
Rails.application.configure do
|
||||||
# Settings specified here will take precedence over those in config/application.rb.
|
# Settings specified here will take precedence over those in config/application.rb.
|
||||||
|
@ -78,31 +77,20 @@ Rails.application.configure do
|
||||||
# config.action_mailer.raise_delivery_errors = false
|
# config.action_mailer.raise_delivery_errors = false
|
||||||
|
|
||||||
if ENV['MAILTRAP_ENABLED'] == 'enabled'
|
if ENV['MAILTRAP_ENABLED'] == 'enabled'
|
||||||
ActionMailer::Base.add_delivery_method :mailtrap, Mailtrap::Smtp
|
|
||||||
config.action_mailer.mailtrap_settings = {
|
|
||||||
user_name: Rails.application.secrets.mailtrap[:username],
|
|
||||||
password: Rails.application.secrets.mailtrap[:password],
|
|
||||||
address: 'smtp.mailtrap.io',
|
|
||||||
domain: 'smtp.mailtrap.io',
|
|
||||||
port: '2525',
|
|
||||||
authentication: :cram_md5
|
|
||||||
}
|
|
||||||
config.action_mailer.delivery_method = :mailtrap
|
config.action_mailer.delivery_method = :mailtrap
|
||||||
elsif
|
|
||||||
if ENV['SENDINBLUE_ENABLED'] == 'enabled'
|
|
||||||
ActionMailer::Base.add_delivery_method :sendinblue, Sendinblue::Smtp
|
|
||||||
config.action_mailer.sendinblue_settings = {
|
|
||||||
user_name: Rails.application.secrets.sendinblue[:username],
|
|
||||||
password: Rails.application.secrets.sendinblue[:smtp_key],
|
|
||||||
address: 'smtp-relay.sendinblue.com',
|
|
||||||
domain: 'smtp-relay.sendinblue.com',
|
|
||||||
port: '587',
|
|
||||||
authentication: :cram_md5
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Default delivery method
|
elsif ENV['SENDINBLUE_ENABLED'] == 'enabled' && ENV['SENDINBLUE_BALANCING'] == 'enabled'
|
||||||
# (Actual delivery method will be selected at runtime by DynamicSmtpSettingsInterceptor)
|
ActionMailer::Base.add_delivery_method :balancer, BalancerDeliveryMethod
|
||||||
|
config.action_mailer.balancer_settings = {
|
||||||
|
sendinblue: ENV.fetch('SENDINBLUE_BALANCING_VALUE').to_i,
|
||||||
|
mailjet: 100 - ENV.fetch('SENDINBLUE_BALANCING_VALUE').to_i
|
||||||
|
}
|
||||||
|
config.action_mailer.delivery_method = :balancer
|
||||||
|
|
||||||
|
elsif ENV['SENDINBLUE_ENABLED'] == 'enabled'
|
||||||
|
config.action_mailer.delivery_method = :sendinblue
|
||||||
|
|
||||||
|
else
|
||||||
config.action_mailer.delivery_method = :mailjet
|
config.action_mailer.delivery_method = :mailjet
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
# We want to register an interceptor, but we can't make the action idempotent
|
|
||||||
# (because there's no way to peek at the currently registered interceptors).
|
|
||||||
#
|
|
||||||
# To make zeitwerk happy, instead signal that we don't want the
|
|
||||||
# DynamicSmtpSettingsInterceptor constant to be auto-loaded, by:
|
|
||||||
# - adding it to a non-autoloaded-path (/lib),
|
|
||||||
# - requiring it explicitely.
|
|
||||||
#
|
|
||||||
# See https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#autoloading-when-the-application-boots
|
|
||||||
require 'action_mailer/dynamic_smtp_settings_interceptor'
|
|
||||||
|
|
||||||
ActiveSupport.on_load(:action_mailer) do
|
|
||||||
ActionMailer::Base.register_interceptor DynamicSmtpSettingsInterceptor
|
|
||||||
end
|
|
17
config/initializers/helo.rb
Normal file
17
config/initializers/helo.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
if ENV['HELO_ENABLED'] == 'enabled'
|
||||||
|
ActiveSupport.on_load(:action_mailer) do
|
||||||
|
module Helo
|
||||||
|
class SMTP < ::Mail::SMTP; end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActionMailer::Base.add_delivery_method :helo, Helo::SMTP
|
||||||
|
ActionMailer::Base.helo_settings = {
|
||||||
|
user_name: 'demarches-simplifiees',
|
||||||
|
password: '',
|
||||||
|
address: '127.0.0.1',
|
||||||
|
domain: '127.0.0.1',
|
||||||
|
port: ENV.fetch('HELO_PORT', '2525'),
|
||||||
|
authentication: :plain
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
17
config/initializers/mailtrap.rb
Normal file
17
config/initializers/mailtrap.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
if ENV.fetch('MAILTRAP_ENABLED') == 'enabled'
|
||||||
|
ActiveSupport.on_load(:action_mailer) do
|
||||||
|
module Mailtrap
|
||||||
|
class SMTP < ::Mail::SMTP; end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActionMailer::Base.add_delivery_method :mailtrap, Mailtrap::SMTP
|
||||||
|
ActionMailer::Base.mailtrap_settings = {
|
||||||
|
user_name: Rails.application.secrets.mailtrap[:username],
|
||||||
|
password: Rails.application.secrets.mailtrap[:password],
|
||||||
|
address: 'smtp.mailtrap.io',
|
||||||
|
domain: 'smtp.mailtrap.io',
|
||||||
|
port: '2525',
|
||||||
|
authentication: :cram_md5
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,5 +1,23 @@
|
||||||
|
if ENV.fetch('SENDINBLUE_ENABLED') == 'enabled'
|
||||||
require 'sib-api-v3-sdk'
|
require 'sib-api-v3-sdk'
|
||||||
|
|
||||||
SibApiV3Sdk.configure do |config|
|
ActiveSupport.on_load(:action_mailer) do
|
||||||
config.api_key['api-key'] = ENV.fetch('SENDINBLUE_API_V3_KEY', '')
|
module Sendinblue
|
||||||
|
class SMTP < ::Mail::SMTP; end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActionMailer::Base.add_delivery_method :sendinblue, Sendinblue::SMTP
|
||||||
|
ActionMailer::Base.sendinblue_settings = {
|
||||||
|
user_name: Rails.application.secrets.sendinblue[:username],
|
||||||
|
password: Rails.application.secrets.sendinblue[:smtp_key],
|
||||||
|
address: 'smtp-relay.sendinblue.com',
|
||||||
|
domain: 'smtp-relay.sendinblue.com',
|
||||||
|
port: '587',
|
||||||
|
authentication: :cram_md5
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
SibApiV3Sdk.configure do |config|
|
||||||
|
config.api_key['api-key'] = Rails.application.secrets.sendinblue[:api_v3_key]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
# Note: this class is instanciated when being added as an interceptor
|
|
||||||
# during the app initialization.
|
|
||||||
#
|
|
||||||
# If you edit this file in development env, you will need to restart
|
|
||||||
# the app to see the changes.
|
|
||||||
|
|
||||||
class DynamicSmtpSettingsInterceptor
|
|
||||||
def self.delivering_email(message)
|
|
||||||
if balance_to_sendinblue?
|
|
||||||
ApplicationMailer.wrap_delivery_behavior(message, :sendinblue)
|
|
||||||
end
|
|
||||||
# fallback to the default delivery method
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def self.balance_to_sendinblue?
|
|
||||||
if ENV.fetch('SENDINBLUE_ENABLED') != 'enabled'
|
|
||||||
false
|
|
||||||
elsif ENV.fetch('SENDINBLUE_BALANCING') == 'enabled'
|
|
||||||
rand(0..99) < ENV.fetch('SENDINBLUE_BALANCING_VALUE').to_i
|
|
||||||
else
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
93
spec/lib/balancer_delivery_method_spec.rb
Normal file
93
spec/lib/balancer_delivery_method_spec.rb
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
RSpec.describe BalancerDeliveryMethod do
|
||||||
|
class ExampleMailer < ApplicationMailer
|
||||||
|
def greet(name)
|
||||||
|
mail(to: "smtp_to", from: "smtp_from", body: "Hello #{name}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TestMail
|
||||||
|
def self.deliveries
|
||||||
|
@deliveries ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.deliveries=(val)
|
||||||
|
@deliveries = val
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_accessor :settings
|
||||||
|
|
||||||
|
def initialize(values)
|
||||||
|
@settings = values.dup
|
||||||
|
end
|
||||||
|
|
||||||
|
def deliver!(mail)
|
||||||
|
Mail::CheckDeliveryParams.check(mail)
|
||||||
|
self.class.deliveries << mail
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class MockSmtp < TestMail; end
|
||||||
|
|
||||||
|
class MockSendmail < TestMail; end
|
||||||
|
|
||||||
|
class FixedSequence
|
||||||
|
def initialize(sequence)
|
||||||
|
@enumerator = sequence.each
|
||||||
|
end
|
||||||
|
|
||||||
|
def rand(_)
|
||||||
|
@enumerator.next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
ActionMailer::Base.add_delivery_method :mock_smtp, MockSmtp
|
||||||
|
ActionMailer::Base.add_delivery_method :mock_sendmail, MockSendmail
|
||||||
|
ActionMailer::Base.add_delivery_method :balancer, BalancerDeliveryMethod
|
||||||
|
|
||||||
|
ExampleMailer.delivery_method = :balancer
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a single delivery method is provided' do
|
||||||
|
before do
|
||||||
|
ActionMailer::Base.balancer_settings = { mock_smtp: 10 }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends emails to the selected delivery method' do
|
||||||
|
mail = ExampleMailer.greet('Joshua').deliver_now
|
||||||
|
expect(mail).to have_been_delivered_using(MockSmtp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when multiple delivery methods are provided' do
|
||||||
|
before do
|
||||||
|
ActionMailer::Base.balancer_settings = { mock_smtp: 10, mock_sendmail: 5 }
|
||||||
|
|
||||||
|
rng_sequence = [3, 14, 1]
|
||||||
|
BalancerDeliveryMethod.random = FixedSequence.new(rng_sequence)
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
BalancerDeliveryMethod.random = Random.new
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends emails randomly, given the provided weights' do
|
||||||
|
mail1 = ExampleMailer.greet('Lucia').deliver_now
|
||||||
|
expect(mail1).to have_been_delivered_using(MockSmtp)
|
||||||
|
|
||||||
|
mail2 = ExampleMailer.greet('Damian').deliver_now
|
||||||
|
expect(mail2).to have_been_delivered_using(MockSendmail)
|
||||||
|
|
||||||
|
mail3 = ExampleMailer.greet('Rahwa').deliver_now
|
||||||
|
expect(mail3).to have_been_delivered_using(MockSmtp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helpers
|
||||||
|
|
||||||
|
def have_been_delivered_using(delivery_class)
|
||||||
|
satisfy("have been delivered using #{delivery_class}") do |mail|
|
||||||
|
delivery_class.deliveries.include?(mail)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue