Merge pull request #6821 from betagouv/improve-email-balancing
Amélioration de la configuration des fournisseurs d'email
This commit is contained in:
commit
a3a2bab89b
10 changed files with 220 additions and 100 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
|
|
@ -70,28 +70,33 @@ MATOMO_ENABLED="disabled"
|
|||
MATOMO_ID=""
|
||||
MATOMO_HOST="matomo.organisme.fr"
|
||||
|
||||
# SMTP Provider: Send In Blue
|
||||
# Default SMTP Provider: Mailjet
|
||||
MAILJET_API_KEY=""
|
||||
MAILJET_SECRET_KEY=""
|
||||
|
||||
# Alternate SMTP Provider: SendInBlue
|
||||
SENDINBLUE_ENABLED="disabled"
|
||||
SENDINBLUE_BALANCING=""
|
||||
SENDINBLUE_BALANCING_VALUE=""
|
||||
SENDINBLUE_CLIENT_KEY=""
|
||||
SENDINBLUE_SMTP_KEY=""
|
||||
SENDINBLUE_USER_NAME=""
|
||||
# SENDINBLUE_LOGIN_URL="https://app.sendinblue.com/account/saml/login/truc"
|
||||
|
||||
# SMTP Provider: Mailjet
|
||||
MAILJET_API_KEY=""
|
||||
MAILJET_SECRET_KEY=""
|
||||
# Ratio of emails sent using SendInBlue
|
||||
# When enabled, N % of emails will be sent using SendInBlue
|
||||
# (and the others using the default SMTP provider)
|
||||
SENDINBLUE_BALANCING="disabled"
|
||||
SENDINBLUE_BALANCING_VALUE="50"
|
||||
|
||||
# Alternate SMTP Provider: Mailtrap (mail catcher for staging environments)
|
||||
# When enabled, all emails will be sent using this provided
|
||||
MAILTRAP_ENABLED="disabled"
|
||||
MAILTRAP_USERNAME=""
|
||||
MAILTRAP_PASSWORD=""
|
||||
|
||||
# External service: live chat for admins (specific to démarches-simplifiées.fr)
|
||||
CRISP_ENABLED="disabled"
|
||||
CRISP_CLIENT_KEY=""
|
||||
|
||||
# External service: mail catcher for staging environments (specific to démarches-simplifiées.fr)
|
||||
MAILTRAP_ENABLED="disabled"
|
||||
MAILTRAP_USERNAME=""
|
||||
MAILTRAP_PASSWORD=""
|
||||
|
||||
# API Entreprise credentials
|
||||
# https://api.gouv.fr/api/api-entreprise.html
|
||||
API_ENTREPRISE_KEY=""
|
||||
|
|
|
@ -75,40 +75,13 @@ Rails.application.configure do
|
|||
config.assets.raise_runtime_errors = true
|
||||
|
||||
# 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 = {
|
||||
host: 'localhost',
|
||||
port: 3000
|
||||
}
|
||||
|
||||
config.action_mailer.asset_host = "http://" + ENV['APP_HOST']
|
||||
end
|
||||
config.action_mailer.default_url_options = {
|
||||
host: 'localhost',
|
||||
port: 3000
|
||||
}
|
||||
config.action_mailer.asset_host = "http://" + ENV['APP_HOST']
|
||||
|
||||
Rails.application.routes.default_url_options = {
|
||||
host: 'localhost',
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require "active_support/core_ext/integer/time"
|
||||
require Rails.root.join("app/lib/balancer_delivery_method")
|
||||
|
||||
Rails.application.configure do
|
||||
# Settings specified here will take precedence over those in config/application.rb.
|
||||
|
@ -76,25 +77,19 @@ Rails.application.configure do
|
|||
# config.action_mailer.raise_delivery_errors = false
|
||||
|
||||
if ENV['MAILTRAP_ENABLED'] == 'enabled'
|
||||
config.action_mailer.delivery_method = :smtp
|
||||
config.action_mailer.smtp_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
|
||||
|
||||
elsif ENV['SENDINBLUE_ENABLED'] == 'enabled' && ENV['SENDINBLUE_BALANCING'] == 'enabled'
|
||||
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 = :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
|
||||
}
|
||||
config.action_mailer.delivery_method = :sendinblue
|
||||
|
||||
else
|
||||
config.action_mailer.delivery_method = :mailjet
|
||||
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 @@
|
|||
require 'sib-api-v3-sdk'
|
||||
if ENV.fetch('SENDINBLUE_ENABLED') == 'enabled'
|
||||
require 'sib-api-v3-sdk'
|
||||
|
||||
SibApiV3Sdk.configure do |config|
|
||||
config.api_key['api-key'] = ENV.fetch('SENDINBLUE_API_V3_KEY', '')
|
||||
ActiveSupport.on_load(:action_mailer) do
|
||||
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
|
||||
|
|
|
@ -1,22 +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 ENV['SENDINBLUE_BALANCING'] == 'enabled'
|
||||
if rand(0..99) < ENV['SENDINBLUE_BALANCING_VALUE'].to_i
|
||||
message.delivery_method.settings = {
|
||||
user_name: ENV['SENDINBLUE_USER_NAME'],
|
||||
password: ENV['SENDINBLUE_SMTP_KEY'],
|
||||
address: 'smtp-relay.sendinblue.com',
|
||||
domain: 'smtp-relay.sendinblue.com',
|
||||
port: '587',
|
||||
authentication: :cram_md5
|
||||
}
|
||||
end
|
||||
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