Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sidekiq + Devise Mailer with multiple Subdomains

I have a problem with changing the ActionMailer::Base.default_url_options = {:host => host} during runtime in one of my projects.

Setup: I have multiple subdomains which are using the same Rails-Application in the backend. I want to send my Devise mails over a Sidekiq queue to the users. The Devise mails (confirmation, reset-password) contain links and these links need the specific subdomain to be correct.

My environment

rails (4.2.0)
sidekiq (3.3.1)
devise (3.4.1)
devise-async (0.9.0)

I have a before_action in my application_controller

class ApplicationController < ActionController::Base

  before_action :set_action_mailer_default_url_options

  private

  def set_action_mailer_default_url_options
    host = "my-logic-to-get-the-correct-host"
    ActionMailer::Base.default_url_options = {:host => host}
  end

end

Now if I want to reset my password I always get the default url options, which I have specified in the environments file. If I remove the default_url_options from my environments I get the default Devise error in my sidekiq logs.

ActionView::Template::Error: Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true

The host is always set correctly in the controller. I already debugged that. It seems that the host is not passed to sidekiq.. any idea how I can fix this?

I can't save the subdomain somewhere, because an user can trigger the emails from different subdomains and not always the same. I need a way to tell Devise which host should be used to send a specific email. Can I override the Devise mailer and pass the host or something similar?

Following solution is not possible in my case: Dynamic domain name in Rails action mailer while using sidekiq

Btw: the complete workflow with devise, devise-async and sidekiq itself works (in development, staging and production). Only the host of the links is not correct -> and that is my big problem :-)..

like image 426
Matthias Avatar asked Feb 13 '15 00:02

Matthias


1 Answers

I ended up in following solution. This solution contains the case that you have multiple customers and these customers could have own email-settings and own domains / subdomains for the urls in the mails.

I used an own mailer class and hooked in possible custom email settings of a customer:

config/initializers/devise.rb:

config.mailer = "DeviseMailer"

config/secrets.yml:

development:
  mailer_settings:
    customers:
      customer_1_identifier_name:
        send:
          address: "smtp.gmail.com"
          port: 587
          domain: "example"
          user_name: "[email protected]"
          email: "[email protected]"
          password: "secret"
          authentication: "plain" # or "login"
          ssl: false # or true
          tls: false # or true
          enable_starttls_auto: true # or false

app/mailers/devise_mailer.rb:

class DeviseMailer < Devise::Mailer
  layout 'mailer'

  after_action :set_email_settings_and_sender, only: [:reset_password_instructions, :confirmation_instructions]

  def reset_password_instructions(record, token, opts={})
    @account = record.current_account if record.class == User # set the account of current_user - current_account is an own helper method
    super
  end

  def confirmation_instructions(record, token, opts={})
    @account = record.current_account if record.class == User && record.unconfirmed_email.present?
    super
  end

  private

    # re-set the email settings for the given user and his account:
    def set_email_settings_and_sender
      if @account.present? && @account.custom_email_settings?
        mail.reply_to = nil
        settings = Rails.application.secrets.mailer_settings["customers"][@account.identifier_name]["send"].symbolize_keys
        mail.delivery_method.settings.merge!(settings)
        mail.from = "#{@account.display_name} <#{settings[:email]}>"
      end
    end

end

Change the urls in the email view templates:

app/views/devise/mailer/confirmation_instructions.html.erb

Simple change the link with the new url-options..

app/views/devise/reset_password_instructions.html.erb

<% if @account.present? && @account.domain.present? %>
  <% link = "#{@account.host_for_links}/auth/password/edit?reset_password_token=#{@token}" %>
  <a href="<%= link %>"><%= link %></a>
<% else %>
  <a href="<%= edit_password_url(@resource, reset_password_token: @token) %>"><%= edit_password_url(@resource, reset_password_token: @token) %></a>
<% end %>

This was my solution.. if you have any question, just let me know.

EDIT: Don't forget to set default url-options in your environment. For example in your development.rb - config/environments/development.rb:

config.action_mailer.default_url_options = { host: "your-host.dev", port: 3000 }
like image 95
Matthias Avatar answered Jan 02 '23 13:01

Matthias