diff --git a/server/Gemfile b/server/Gemfile index 10d6088a..5c69e2c1 100644 --- a/server/Gemfile +++ b/server/Gemfile @@ -90,3 +90,4 @@ end # gem "image_processing", "~> 1.2" # For image variants if you decide to use Active Storage # NOTE: Removed unused or commented-out gems for better readability. +gem "premailer-rails" diff --git a/server/Gemfile.lock b/server/Gemfile.lock index 3e6300ba..dfb91848 100644 --- a/server/Gemfile.lock +++ b/server/Gemfile.lock @@ -1664,6 +1664,8 @@ GEM fiber-local json crass (1.0.6) + css_parser (1.17.1) + addressable csv (3.3.0) date (3.3.4) declarative (0.0.20) @@ -1805,6 +1807,7 @@ GEM googleapis-common-protos-types (~> 1.0) hana (1.3.7) hashie (5.0.0) + htmlentities (4.3.4) httpclient (2.8.3) hubspot-api-client (18.0.0) json (~> 2.1, >= 2.1.0) @@ -1913,6 +1916,14 @@ GEM pg (1.5.4) pg_query (5.0.0) google-protobuf (>= 3.22.3) + premailer (1.23.0) + addressable + css_parser (>= 1.12.0) + htmlentities (>= 4.0.0) + premailer-rails (1.12.0) + actionmailer (>= 3) + net-smtp + premailer (~> 1.7, >= 1.7.9) protocol-hpack (1.4.3) protocol-http (0.26.1) protocol-http1 (0.18.0) @@ -2124,6 +2135,7 @@ DEPENDENCIES parallel pg (~> 1.1) pg_query + premailer-rails puma (>= 5.0) rack-cors rails (~> 7.1.1) diff --git a/server/app/mailers/sync_run_mailer.rb b/server/app/mailers/sync_run_mailer.rb index 4b983aba..0671f232 100644 --- a/server/app/mailers/sync_run_mailer.rb +++ b/server/app/mailers/sync_run_mailer.rb @@ -1,9 +1,18 @@ +# frozen_string_literal: true + class SyncRunMailer < ApplicationMailer - default from: ENV['SMTP_USERNAME'] + default from: Rails.configuration.x.mail_from def status_email @sync_run = params[:sync_run] + @sync = @sync_run.sync @sync_status = @sync_run.status - mail(to: params[:recipient], subject: "Sync run status update") + @source_connector_name = @sync_run.sync.source.name + @destination_connector_name = @sync_run.sync.destination.name + host = Rails.configuration.action_mailer.default_url_options[:host] + @sync_run_url = "#{host}/activate/syncs/#{@sync.id}/run/#{@sync_run.id}" + mail(to: params[:recipient], subject: "Sync run status update") do |format| + format.html { render layout: false } + end end end diff --git a/server/app/models/application_record.rb b/server/app/models/application_record.rb index 08dc5379..c9dc8988 100644 --- a/server/app/models/application_record.rb +++ b/server/app/models/application_record.rb @@ -2,4 +2,9 @@ class ApplicationRecord < ActiveRecord::Base primary_abstract_class + + def notification_email_enabled? + Rails.configuration.action_mailer.perform_deliveries && + Rails.configuration.action_mailer.smtp_settings[:user_name].present? + end end diff --git a/server/app/models/sync_run.rb b/server/app/models/sync_run.rb index 870cb184..fca78571 100644 --- a/server/app/models/sync_run.rb +++ b/server/app/models/sync_run.rb @@ -28,6 +28,7 @@ class SyncRun < ApplicationRecord after_initialize :set_defaults, if: :new_record? after_discard :perform_post_discard_sync_run + after_commit :send_status_email, if: :status_changed_to_success_or_failed? aasm column: :status, whiny_transitions: true do state :pending, initial: true @@ -89,4 +90,24 @@ def update_success complete! sync.complete! end + + def send_status_email + return unless notification_email_enabled? + + recipients.each do |recipient| + SyncRunMailer.with(sync_run: self, recipient:).status_email.deliver_now + end + end + + def recipients + if ENV["RECIPIENT_EMAIL"].present? + [ENV["RECIPIENT_EMAIL"]] + else + sync.workspace.workspace_users.admins.map { |workspace_user| workspace_user.user.email } + end + end + + def status_changed_to_success_or_failed? + saved_change_to_status? && (status == "success" || status == "failed") + end end diff --git a/server/app/views/sync_run_mailer/status_email.html.erb b/server/app/views/sync_run_mailer/status_email.html.erb index 71bbb07f..54753a6c 100644 --- a/server/app/views/sync_run_mailer/status_email.html.erb +++ b/server/app/views/sync_run_mailer/status_email.html.erb @@ -1,8 +1,8 @@ +
-

Salesforce to SFTP Test job was <%= @sync_status == "success" ? 'successful' : 'failure' %>. - Please click the Sync Run link below to view more details. -

+

<%= @source_connector_name %> to <%= @destination_connector_name %> job was <%= @sync_status == "success" ? "successful" : "failure" %>. Please click the Sync Run link below to view more details.

- Sync Run + Sync Run
diff --git a/server/config/application.rb b/server/config/application.rb index 2307648c..e9283072 100644 --- a/server/config/application.rb +++ b/server/config/application.rb @@ -47,19 +47,20 @@ class Application < Rails::Application # Skip views, helpers and assets when generating a new resource. config.api_only = true config.middleware.insert_before Rails::Rack::Logger, MultiwovenServer::QuietLogger - # email setup + config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :smtp - host = ENV.fetch('SMTP_HOST', 'multiwoven.com') # Replace 'multiwoven.com' with SMTP_HOST value if needed + config.action_mailer.perform_deliveries = true + host = ENV.fetch('SMTP_HOST', 'multiwoven.com') config.action_mailer.default_url_options = { host: host } - config.x.mail_from = %("Multiwoven" ) - ActionMailer::Base.smtp_settings = { - address: ENV['SMTP_ADDRESS'], - port: ENV.fetch('SMTP_PORT', '587'), # '587' is the default value if SMTP_PORT is not set - authentication: :plain, + config.x.mail_from = "#{ENV.fetch('BRAND_NAME', 'Multiwoven')} <#{ENV.fetch('SMTP_SENDER_EMAIL', 'noreply@multiwoven.com')}>" + config.action_mailer.smtp_settings = { + address: ENV['SMTP_ADDRESS'], + port: ENV.fetch('SMTP_PORT', '587'), + authentication: :login, user_name: ENV['SMTP_USERNAME'], password: ENV['SMTP_PASSWORD'], + enable_starttls_auto: true } - config.action_mailer.default_url_options = { host: ENV['UI_HOST'] || 'localhost:8000' } end end \ No newline at end of file diff --git a/server/config/initializers/premailer_rails.rb b/server/config/initializers/premailer_rails.rb new file mode 100644 index 00000000..d29a0323 --- /dev/null +++ b/server/config/initializers/premailer_rails.rb @@ -0,0 +1,4 @@ +Premailer::Rails.config.merge!( + adapter: :nokogiri, + generate_text_part: false +) diff --git a/server/spec/mailers/sync_run_mailer_spec.rb b/server/spec/mailers/sync_run_mailer_spec.rb index 4c1b6318..6269c843 100644 --- a/server/spec/mailers/sync_run_mailer_spec.rb +++ b/server/spec/mailers/sync_run_mailer_spec.rb @@ -1,5 +1,42 @@ +# frozen_string_literal: true + require "rails_helper" RSpec.describe SyncRunMailer, type: :mailer do - pending "add some examples to (or delete) #{__FILE__}" + describe "#status_email" do + let(:source) do + create(:connector, connector_type: "source", connector_name: "Snowflake") + end + let(:destination) { create(:connector, connector_type: "destination") } + let!(:catalog) { create(:catalog, connector: destination) } + let(:sync) { create(:sync, source:, destination:) } + let(:sync_run) do + create(:sync_run, sync:, workspace: sync.workspace, source:, destination:, model: sync.model, status: "success") + end + let(:sync_run_pending) do + create(:sync_run, sync:, workspace: sync.workspace, source:, destination:, model: sync.model, status: "pending") + end + let(:recipient) { "test@example.com" } + let(:mail) { SyncRunMailer.with(sync_run:, recipient:).status_email } + + it "renders the headers" do + expect(mail.subject).to eq("Sync run status update") + expect(mail.to).to eq([recipient]) + expect(mail.from).to eq(["noreply@multiwoven.com"]) + end + + it "renders the body" do + expect(mail.body.encoded).to match(sync_run.status) + expect(mail.body.encoded).to match(sync.source.name) + expect(mail.body.encoded).to match(sync.destination.name) + host = Rails.configuration.action_mailer.default_url_options[:host] + url = "#{host}/activate/syncs/#{sync.id}/run/#{sync_run.id}" + expect(mail.body.encoded).to match(url) + end + + it "assigns @sync_run_url" do + expect(mail.body.encoded).to match(sync_run.sync.id.to_s) + expect(mail.body.encoded).to match(sync_run.id.to_s) + end + end end diff --git a/server/spec/models/sync_run_spec.rb b/server/spec/models/sync_run_spec.rb index 64f21caa..06758dfe 100644 --- a/server/spec/models/sync_run_spec.rb +++ b/server/spec/models/sync_run_spec.rb @@ -148,4 +148,32 @@ expect(sync_record.sync_run_id).to be_nil end end + + describe "#send_status_email" do + let(:source) do + create(:connector, connector_type: "source", connector_name: "Snowflake") + end + let(:destination) { create(:connector, connector_type: "destination") } + let!(:catalog) { create(:catalog, connector: destination) } + let(:sync) { create(:sync, sync_interval: 3, sync_interval_unit: "hours", source:, destination:) } + let(:sync_run) { create(:sync_run, sync:, status: :pending) } + + it "calls send_status_email after commit when status changes to success" do + allow(sync_run).to receive(:send_status_email) + sync_run.update!(status: :success) + expect(sync_run).to have_received(:send_status_email) + end + + it "calls send_status_email after commit when status changes to failed" do + allow(sync_run).to receive(:send_status_email) + sync_run.update!(status: :failed) + expect(sync_run).to have_received(:send_status_email) + end + + it "does not call send_status_email if status does not change to success or failed" do + allow(sync_run).to receive(:send_status_email) + sync_run.update!(status: :in_progress) + expect(sync_run).not_to have_received(:send_status_email) + end + end end