diff --git a/app/controllers/manager/application_controller.rb b/app/controllers/manager/application_controller.rb index badd07636..f529fa7cf 100644 --- a/app/controllers/manager/application_controller.rb +++ b/app/controllers/manager/application_controller.rb @@ -4,7 +4,7 @@ module Manager before_action :default_params def default_params - params[resource_name] ||= { + request.query_parameters[resource_name] ||= { order: "created_at", direction: "desc" } diff --git a/app/controllers/manager/email_events_controller.rb b/app/controllers/manager/email_events_controller.rb new file mode 100644 index 000000000..bd7b59ff7 --- /dev/null +++ b/app/controllers/manager/email_events_controller.rb @@ -0,0 +1,4 @@ +module Manager + class EmailEventsController < Manager::ApplicationController + end +end diff --git a/app/dashboards/email_event_dashboard.rb b/app/dashboards/email_event_dashboard.rb new file mode 100644 index 000000000..156e0f595 --- /dev/null +++ b/app/dashboards/email_event_dashboard.rb @@ -0,0 +1,24 @@ +require "administrate/base_dashboard" + +class EmailEventDashboard < Administrate::BaseDashboard + ATTRIBUTE_TYPES = { + id: Field::Number, + to: Field::String, + subject: Field::String, + method: Field::Enum, + status: Field::Enum, + processed_at: Field::DateTime.with_options(format: "%F %T") + } + COLLECTION_ATTRIBUTES = [:id, :to, :subject, :method, :status, :processed_at].freeze + SHOW_PAGE_ATTRIBUTES = [:id, :to, :subject, :method, :status, :processed_at].freeze + + METHODS_FILTERS = + ActionMailer::Base.delivery_methods.keys.index_with do |method| + -> (resources) { resources.where(method: method) } + end + + COLLECTION_FILTERS = { + dispatched: -> (resources) { resources.dispatched }, + dispatch_error: -> (resources) { resources.dispatch_error } + }.merge(METHODS_FILTERS).freeze +end diff --git a/app/lib/mailjet/api.rb b/app/lib/mailjet/api.rb index e97845cdf..92e135160 100644 --- a/app/lib/mailjet/api.rb +++ b/app/lib/mailjet/api.rb @@ -1,6 +1,6 @@ class Mailjet::API def properly_configured? - [Mailjet.config.api_key, Mailjet.config.secret_key].all?(&:present?) + Mailjet.respond_to?(:config) && [Mailjet.config.api_key, Mailjet.config.secret_key].all?(&:present?) end # Get messages sent to a user through SendInBlue. diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index a09385116..7fc719874 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,21 +1,12 @@ class ApplicationMailer < ActionMailer::Base + include MailerMonitoringConcern + helper :application # gives access to all helpers defined within `application_helper`. default from: "#{APPLICATION_NAME} <#{CONTACT_EMAIL}>" layout 'mailer' before_action :add_dolist_header - # Don’t retry to send a message if the server rejects the recipient address - rescue_from Net::SMTPSyntaxError do |_error| - message.perform_deliveries = false - end - - rescue_from Net::SMTPServerBusy do |error| - if /unexpected recipients/.match?(error.message) - message.perform_deliveries = false - end - end - # Attach the procedure logo to the email (if any). # Returns the attachment url. def attach_logo(procedure) @@ -24,18 +15,9 @@ class ApplicationMailer < ActionMailer::Base attachments.inline[logo_filename] = procedure.logo.download attachments[logo_filename].url end - rescue StandardError => e # A problem occured when reading logo, maybe the logo is missing and we should clean the procedure to remove logo reference ? Sentry.capture_exception(e, extra: { procedure_id: procedure.id }) nil end - - # mandatory for dolist - # used for tracking in Dolist UI - # the delivery_method is yet unknown (:balancer) - # so we add the dolist header for everyone - def add_dolist_header - headers['X-Dolist-Message-Name'] = action_name - end end diff --git a/app/mailers/concerns/mailer_monitoring_concern.rb b/app/mailers/concerns/mailer_monitoring_concern.rb new file mode 100644 index 000000000..8dfd84179 --- /dev/null +++ b/app/mailers/concerns/mailer_monitoring_concern.rb @@ -0,0 +1,37 @@ +module MailerMonitoringConcern + extend ActiveSupport::Concern + + included do + # Don’t retry to send a message if the server rejects the recipient address + rescue_from Net::SMTPSyntaxError do |_exception| + message.perform_deliveries = false + end + + rescue_from Net::SMTPServerBusy do |exception| + if /unexpected recipients/.match?(exception.message) + message.perform_deliveries = false + else + log_delivery_error(exception) + end + end + + rescue_from StandardError, with: :log_delivery_error + + # mandatory for dolist + # used for tracking in Dolist UI + # the delivery_method is yet unknown (:balancer) + # so we add the dolist header for everyone + def add_dolist_header + headers['X-Dolist-Message-Name'] = action_name + end + + protected + + def log_delivery_error(exception) + EmailEvent.create_from_message!(message, status: "dispatch_error") + Sentry.capture_exception(exception, extra: { to: message.to, subject: message.subject }) + + # TODO find a way to re attempt the job + end + end +end diff --git a/app/mailers/devise_user_mailer.rb b/app/mailers/devise_user_mailer.rb index 964fa2c5e..77986e421 100644 --- a/app/mailers/devise_user_mailer.rb +++ b/app/mailers/devise_user_mailer.rb @@ -3,19 +3,9 @@ class DeviseUserMailer < Devise::Mailer helper :application # gives access to all helpers defined within `application_helper`. helper MailerHelper include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url` + include MailerMonitoringConcern layout 'mailers/layout' - # Don’t retry to send a message if the server rejects the recipient address - rescue_from Net::SMTPSyntaxError do |_error| - message.perform_deliveries = false - end - - rescue_from Net::SMTPServerBusy do |error| - if /unexpected recipients/.match?(error.message) - message.perform_deliveries = false - end - end - def template_paths ['devise_mailer'] end diff --git a/app/models/email_event.rb b/app/models/email_event.rb new file mode 100644 index 000000000..fca749ff7 --- /dev/null +++ b/app/models/email_event.rb @@ -0,0 +1,37 @@ +# == Schema Information +# +# Table name: email_events +# +# id :bigint not null, primary key +# method :string not null +# processed_at :datetime +# status :string not null +# subject :string not null +# to :string not null +# created_at :datetime not null +# updated_at :datetime not null +# +class EmailEvent < ApplicationRecord + enum status: { + dispatched: 'dispatched', + dispatch_error: 'dispatch_error' + } + + class << self + def create_from_message!(message, status:) + to = message.to || ["unset"] # no recipients when error occurs *before* setting to: in the mailer + + to.each do |recipient| + EmailEvent.create!( + to: recipient, + subject: message.subject, + processed_at: message.date, + method: ActionMailer::Base.delivery_methods.key(message.delivery_method.class), + status: + ) + rescue StandardError => error + Sentry.capture_exception(error, extra: { subject: message.subject, status: }) + end + end + end +end diff --git a/app/services/email_delivery_observer.rb b/app/services/email_delivery_observer.rb new file mode 100644 index 000000000..c297dcf85 --- /dev/null +++ b/app/services/email_delivery_observer.rb @@ -0,0 +1,5 @@ +class EmailDeliveryObserver + def self.delivered_email(message) + EmailEvent.create_from_message!(message, status: "dispatched") + end +end diff --git a/app/views/manager/users/emails.html.erb b/app/views/manager/users/emails.html.erb index 1b9991d72..44b9cdb3e 100644 --- a/app/views/manager/users/emails.html.erb +++ b/app/views/manager/users/emails.html.erb @@ -77,6 +77,8 @@
<% end %> +<%= link_to "Voir aussi les événements d'email", manager_email_events_path("search" => @user.email) %> +