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_ID=""
|
||||||
MATOMO_HOST="matomo.organisme.fr"
|
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_ENABLED="disabled"
|
||||||
SENDINBLUE_BALANCING=""
|
|
||||||
SENDINBLUE_BALANCING_VALUE=""
|
|
||||||
SENDINBLUE_CLIENT_KEY=""
|
SENDINBLUE_CLIENT_KEY=""
|
||||||
SENDINBLUE_SMTP_KEY=""
|
SENDINBLUE_SMTP_KEY=""
|
||||||
SENDINBLUE_USER_NAME=""
|
SENDINBLUE_USER_NAME=""
|
||||||
# SENDINBLUE_LOGIN_URL="https://app.sendinblue.com/account/saml/login/truc"
|
# SENDINBLUE_LOGIN_URL="https://app.sendinblue.com/account/saml/login/truc"
|
||||||
|
|
||||||
# SMTP Provider: Mailjet
|
# Ratio of emails sent using SendInBlue
|
||||||
MAILJET_API_KEY=""
|
# When enabled, N % of emails will be sent using SendInBlue
|
||||||
MAILJET_SECRET_KEY=""
|
# (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)
|
# External service: live chat for admins (specific to démarches-simplifiées.fr)
|
||||||
CRISP_ENABLED="disabled"
|
CRISP_ENABLED="disabled"
|
||||||
CRISP_CLIENT_KEY=""
|
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
|
# API Entreprise credentials
|
||||||
# https://api.gouv.fr/api/api-entreprise.html
|
# https://api.gouv.fr/api/api-entreprise.html
|
||||||
API_ENTREPRISE_KEY=""
|
API_ENTREPRISE_KEY=""
|
||||||
|
|
|
@ -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.default_url_options = {
|
||||||
config.action_mailer.delivery_method = :smtp
|
host: 'localhost',
|
||||||
config.action_mailer.smtp_settings = {
|
port: 3000
|
||||||
user_name: Rails.application.secrets.sendinblue[:username],
|
}
|
||||||
password: Rails.application.secrets.sendinblue[:smtp_key],
|
config.action_mailer.asset_host = "http://" + ENV['APP_HOST']
|
||||||
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
|
|
||||||
|
|
||||||
Rails.application.routes.default_url_options = {
|
Rails.application.routes.default_url_options = {
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
require "active_support/core_ext/integer/time"
|
require "active_support/core_ext/integer/time"
|
||||||
|
require Rails.root.join("app/lib/balancer_delivery_method")
|
||||||
|
|
||||||
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.
|
||||||
|
@ -76,25 +77,19 @@ 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'
|
||||||
config.action_mailer.delivery_method = :smtp
|
config.action_mailer.delivery_method = :mailtrap
|
||||||
config.action_mailer.smtp_settings = {
|
|
||||||
user_name: Rails.application.secrets.mailtrap[:username],
|
elsif ENV['SENDINBLUE_ENABLED'] == 'enabled' && ENV['SENDINBLUE_BALANCING'] == 'enabled'
|
||||||
password: Rails.application.secrets.mailtrap[:password],
|
ActionMailer::Base.add_delivery_method :balancer, BalancerDeliveryMethod
|
||||||
address: 'smtp.mailtrap.io',
|
config.action_mailer.balancer_settings = {
|
||||||
domain: 'smtp.mailtrap.io',
|
sendinblue: ENV.fetch('SENDINBLUE_BALANCING_VALUE').to_i,
|
||||||
port: '2525',
|
mailjet: 100 - ENV.fetch('SENDINBLUE_BALANCING_VALUE').to_i
|
||||||
authentication: :cram_md5
|
|
||||||
}
|
}
|
||||||
|
config.action_mailer.delivery_method = :balancer
|
||||||
|
|
||||||
elsif ENV['SENDINBLUE_ENABLED'] == 'enabled'
|
elsif ENV['SENDINBLUE_ENABLED'] == 'enabled'
|
||||||
config.action_mailer.delivery_method = :smtp
|
config.action_mailer.delivery_method = :sendinblue
|
||||||
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
|
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 @@
|
||||||
require 'sib-api-v3-sdk'
|
if ENV.fetch('SENDINBLUE_ENABLED') == 'enabled'
|
||||||
|
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,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…
Add table
Reference in a new issue