class MailingsController < ApplicationController before_action :require_current_project! before_action :set_mailing, only: %i[show edit update destroy queue cancel test_send test_preview refresh_recipients update_recipients preview] before_action :load_form_collections, only: %i[new create edit update] def index redirect_to dashboard_mailings_path end def dashboard @page_title = "Invii email" @mailings = Mailing.for_project(current_project).includes(:mail_identity).recent @mail_identities_count = MailIdentity.active.count @active_mailings = @mailings.select(&:sending?) @paused_mailings = @active_mailings.select { |mailing| mailing.next_send_at.present? && mailing.next_send_at > Time.current } @completed_today = @mailings.count { |mailing| mailing.sent? && mailing.completed_at&.to_date == Time.zone.today } @failed_open = @mailings.sum(&:failed_count) end def new @page_title = "Nuovo invio" template = MailTemplate.for_project(current_project).order(:name).first identity = MailIdentity.active.ordered.first @mailing = Mailing.new( project: current_project, mail_template: template, mail_identity: identity, audience: "to_send", interval_seconds: 0, send_window_start_minutes: 10 * 60, send_window_end_minutes: 18 * 60, ab_assignment: "from_record", name: "Invio #{l(Time.zone.today)}", subject: template&.subject, body_html: template&.body_html ) end def create @mailing = Mailing.new(mailing_params) @mailing.project = current_project apply_template_if_needed if @mailing.save @mailing.rebuild_recipients! redirect_to @mailing, notice: "Bozza creata. Controlla i destinatari e poi invia." else @page_title = "Nuovo invio" render :new, status: :unprocessable_entity end end def show @page_title = @mailing.name @recipients = @mailing.mailing_recipients.includes(:organization, :contact).ordered @preview_recipient = preview_recipient @preview_variant = preview_variant context = @preview_recipient&.merge_context || { project: current_project, ab_variant: @preview_variant } @preview_html = MailMerge.render(@mailing.body_for(@preview_variant), **context) @preview_subject = MailMerge.render(@mailing.subject_for(@preview_variant), **context) assign_test_preview end def edit unless @mailing.editable? redirect_to @mailing, alert: "Questo invio non è più modificabile." return end @page_title = "Modifica invio" end def update unless @mailing.editable? redirect_to @mailing, alert: "Questo invio non è più modificabile." return end @mailing.assign_attributes(mailing_params) apply_template_if_needed if @mailing.save rebuild_recipients_if_needed redirect_to @mailing, notice: "Invio aggiornato." else @page_title = "Modifica invio" render :edit, status: :unprocessable_entity end end def destroy unless @mailing.draft? redirect_to dashboard_mailings_path, alert: "Puoi eliminare solo le bozze." return end @mailing.destroy! redirect_to dashboard_mailings_path, notice: "Invio eliminato." end def refresh_recipients unless @mailing.editable? redirect_to @mailing, alert: "Destinatari bloccati: l'invio è già partito." return end @mailing.rebuild_recipients! redirect_to @mailing, notice: "Lista destinatari aggiornata." end def update_recipients unless @mailing.editable? redirect_to @mailing, alert: "Non puoi più cambiare i destinatari." return end selected = Array(params[:pending_ids]).map(&:to_i) @mailing.mailing_recipients.where.not(status: %w[sent queued]).find_each do |recipient| if selected.include?(recipient.id) && recipient.email_ok? recipient.update!(status: "pending", skip_reason: nil) else reason = recipient.email_ok? ? "escluso dal check" : (recipient.skip_reason.presence || "email non valida") recipient.update!(status: "skipped", skip_reason: reason) end end redirect_to @mailing, notice: "Selezione destinatari salvata." end def queue unless @mailing.draft? || @mailing.sending? redirect_to @mailing, alert: "Questo invio è già stato concluso." return end unless @mailing.tested? redirect_to @mailing, alert: "Prima di autorizzare l'invio di massa, manda una email di prova con dati di test." return end @mailing.queue_send! redirect_to dashboard_mailings_path, notice: queue_notice(@mailing) rescue StandardError => e redirect_to @mailing, alert: e.message end def cancel unless @mailing.sending? redirect_to @mailing, alert: "Solo un invio in corso può essere fermato." return end pending = @mailing.pending_count + @mailing.queued_count @mailing.cancel_send! redirect_to dashboard_mailings_path, notice: "Invio fermato. #{@mailing.sent_count} già inviate, #{pending} annullate in coda." rescue StandardError => e redirect_to @mailing, alert: e.message end def test_send assign_test_preview to = @test_to.to_s.strip unless to.match?(URI::MailTo::EMAIL_REGEXP) redirect_to @mailing, alert: "Inserisci un indirizzo email valido per la prova." return end html = @test_preview_html subject = "[TEST#{@mailing.ab_test? ? " #{@test_variant}" : ""}] #{@test_preview_subject}" recipient = Mailings::TestRecipient.new(mailing: @mailing, email: to) CampaignMailer.outreach(recipient, html: html, subject: subject).deliver_now @mailing.update!(test_sent_at: Time.current, test_sent_to: to) redirect_to @mailing, notice: "Prova inviata subito a #{to}. Controlla l'output e, se è corretto, autorizza l'invio di massa." rescue StandardError => e redirect_to @mailing, alert: "Invio di prova non riuscito: #{e.message}" end def test_preview assign_test_preview end def preview redirect_to mailing_path(@mailing, recipient_id: params[:recipient_id]) end private def set_mailing @mailing = Mailing.for_project(current_project).find(params[:id]) end def load_form_collections @mail_identities = MailIdentity.active.ordered @mail_templates = MailTemplate.for_project(current_project) end def mailing_params params.require(:mailing).permit( :name, :mail_identity_id, :mail_template_id, :mail_template_b_id, :audience, :subject, :body_html, :subject_b, :body_html_b, :ab_test, :ab_assignment, :interval_seconds, :send_window_enabled, :send_window_start_tod, :send_window_end_tod, files: [] ) end def queue_notice(mailing) pause = mailing.interval_seconds.positive? ? " con pausa di #{mailing.interval_seconds}s" : "" if mailing.send_window_enabled? && mailing.next_send_at && mailing.next_send_at > Time.current "Invio avviato. La prima email partirà #{I18n.l(mailing.next_send_at, format: :long)} (fascia #{mailing.send_window_label}#{pause})." else "Invio avviato. Le email partono in background#{pause}." end end def apply_template_if_needed copy_template_if_blank(params.dig(:mailing, :mail_template_id), :subject, :body_html) copy_template_if_blank(params.dig(:mailing, :mail_template_b_id), :subject_b, :body_html_b) end def copy_template_if_blank(template_id, subject_attr, body_attr) return if template_id.blank? template = MailTemplate.for_project(current_project).find_by(id: template_id) return unless template @mailing.public_send("#{subject_attr}=", template.subject) if @mailing.public_send(subject_attr).blank? @mailing.public_send("#{body_attr}=", template.body_html) if @mailing.public_send(body_attr).blank? end def rebuild_recipients_if_needed return unless @mailing.editable? return unless @mailing.saved_change_to_audience? || @mailing.saved_change_to_ab_test? || @mailing.saved_change_to_ab_assignment? @mailing.rebuild_recipients! end def preview_recipient if params[:recipient_id].present? @mailing.mailing_recipients.find_by(id: params[:recipient_id]) else @mailing.mailing_recipients.pending.ordered.first || @mailing.mailing_recipients.ordered.first end end def preview_variant requested = params[:variant].to_s.upcase return requested if requested.in?(%w[A B]) return "A" unless @mailing.ab_test? preview_recipient&.ab_variant.presence_in(%w[A B]) || "A" end def assign_test_preview @test_to = test_send_params[:to].presence || current_user.email @test_variant = test_send_params[:variant].to_s.upcase.presence_in(%w[A B]) || preview_variant @test_vars = default_test_vars.merge(test_vars_params) extras = @test_vars @test_preview_html = MailMerge.render(@mailing.body_for(@test_variant), extras: extras, project: current_project, ab_variant: @test_variant) @test_preview_subject = MailMerge.render(@mailing.subject_for(@test_variant), extras: extras, project: current_project, ab_variant: @test_variant) end def default_test_vars source = preview_recipient if source MailMerge.variables_for(**source.merge_context.merge(ab_variant: preview_variant)) else MailMerge.catalog.keys.index_with { "" } end end def test_send_params params.permit(:to, :variant, :recipient_id) end def test_vars_params raw = params[:vars] return {} unless raw.respond_to?(:to_unsafe_h) || raw.is_a?(Hash) allowed = MailMerge.catalog.keys hash = raw.respond_to?(:to_unsafe_h) ? raw.to_unsafe_h : raw hash.stringify_keys.slice(*allowed).transform_values { |value| value.to_s } end end