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.
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