Override to field in ActionMailer based on environment - ruby-on-rails

I'm using Rails 4.2 want to override the to field for all ActionMailer mailers for a certain environment. In this case I want to override the to field for all mailers used in Staging. My goal is for the staging environment to deliver mail exactly the same way as production, but to dump it all into a testing inbox.
I know there are services that assist with this, but my goal is to use my production API for staging delivery as a thorough test.
I'm hoping I can use a mixin or something to reset the to field before the mailer fires off.

Not sure what version of Rails you are using, but you might consider using the new mail interceptors to accomplish this.
Main advantage is that it doesn't clutter your ActionMailer classes directly.
http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails
Copying their example:
class SandboxEmailInterceptor
def self.delivering_email(message)
message.to = ['sandbox#example.com']
end
end
config/initializers/sandbox_email_interceptor.rb:
ActionMailer::Base.register_interceptor(SandboxEmailInterceptor) if Rails.env.staging?

The simplest way would be to check which environment is running and set the to field accordingly. For example, a simple password reset mailer might look something like:
class UserMailer < ActionMailer::Base
default from: "support#example.com"
def reset_password(user_id)
#user = User.find(user_id)
#url = reset_password_users_url(token: #user.password_reset_token)
mail(to: #user.email, subject: '[Example] Please reset your password')
end
end
Now to check for the staging environment and route all of these emails to admin#example.com:
class UserMailer < ActionMailer::Base
default from: "support#example.com"
def reset_password(user_id)
#user = User.find(user_id)
#url = reset_password_users_url(token: #user.password_reset_token)
to = Rails.env.staging? ? 'admin#example.com' : #user.email
mail(to: to, subject: '[Example] Please reset your password')
end
end

Related

Override mail function in rails ActionMailer

I use this class for Action Mailer:
class UserMailer < ActionMailer::Base
default from: "something#example.com",
reply_to: 'whatever#example.com'
def mail_method
mail(to: 'email#example.com', subject: "SUBJECT")
end
end
So like this I got many classes and methods which send emails like this from smtp delivery method.
But now I want to perform_deliveries , i.e. send emails only on production environment not in development or test environment.
So for that I want to use my email only, which is why I need to override mail method.
Things I have tried.
-> Making a function to return email, where function name is get_right_email
class UserMailer < ActionMailer::Base
default from: "something#example.com",
reply_to: 'whatever#example.com'
def mail_method
mail(to: get_right_email('email#example.com'), subject: "SUBJECT")
end
end
And definition of get_right_email is as follows:
def get_right_email(email)
if(Rails.env=='production')
return email
else
return 'myPersonalEmail#example.com'
end
end
It would need some refactoring but it is still manageable. Will take a few hours and I can do, but is there a quicker way where I can just override mail function.
In your config > enviroments folder you should have a file for production, development and test. Here you can specify your settings for each one.
Settings in these folders overide those in config > application.rb
For example when testing I don't usually actually send the emails but I do want to be able to test the emails so I use
config.action_mailer.delivery_method = :test
This makes the emails accessible by calling ActionMailer::Base.deliveries
You can set the default from email address in these files as well using:
config.action_mailer.default_options = {
:from => "foo#bar.com"
}
Theres a gem called letter_opener managed by the fantastic Ryan Bates which instead of sending an email in development opens the email in a new tab. This makes testing out emails in development a breeze.
Letter Opener
UPDATE BELOW ------
Apologies, I didn't quite follow what you were looking for.
Rails has webhooks you can use to intercept emails and redirect them. You'll want to use an environment different than production.
The test environment is typically used for automated testing, to keep things clear you might want to consider setting up a new environment (eg: staging).
To create a new environment just create a new file in config/environments/ and give it a suitable name - eg: staging.rb
You can then call Rails.env.staging? where ever you like.
Anyway back to the main event...
To intercept the emails first create an intercept class:
class StagingEmailInterceptor
def self.delivering_email(message)
message.to = ['my#email.com']
end
end
and then create an initializer file, eg:
config/initializers/staging_email_interceptor.rb
and inside do this:
if Rails.env.staging?
ActionMailer::Base.register_interceptor(StagingEmailInterceptor)
end
That way all emails sent in the staging environment will be sent to your email.

Best practice on structuring asynchronous mailers (using Sidekiq)

Just wondering what's the best way to go about structuring asynchronous mailers in my Rails app (using Sidekiq)? I have one ActionMailer class with multiple methods/emails...
notifier.rb:
class Notifier < ActionMailer::Base
default from: "\"Company Name\" <notify#domain.com>"
default_url_options[:host] = Rails.env.production? ? 'domain.com' : 'localhost:5000'
def welcome_email(user)
#user = user
mail to: #user.email, subject: "Thanks for signing up!"
end
...
def password_reset(user)
#user = user
#edit_password_reset_url = edit_password_reset_url(user.perishable_token)
mail to: #user.email, subject: "Password Reset"
end
end
Then for example, the password_reset mail is sent in my User model by doing...
user.rb:
def deliver_password_reset_instructions!
reset_perishable_token!
NotifierWorker.perform_async(self)
end
notifier_worker.rb:
class NotifierWorker
include Sidekiq::Worker
sidekiq_options queue: "mail"
def perform(user)
Notifier.password_reset(user).deliver
end
end
So I guess I'm wondering a couple things here...
Is it possible to define many "perform" actions in one single worker? By doing so I could keep things simple (one notifier/mail worker) as I have it and send many different emails through it. Or should I create many workers? One for each mailer (e.g. WelcomeEmailWorker, PasswordResetWorker, etc) and just assign them all to use the same "mail" queue with Sidekiq.
I know it works as it is, but should I break out each of those mail methods (welcome_email, password_reset, etc) into individually mailer classes or is it ok to have them all under one class like Notifier?
Really appreciate any advice here. Thanks!
As discussed here, Sidekiq supports delayed mailer by default, so there is no need to create separate workers:
Notifier.delay.password_reset(user.id)
I am not sure but I think its not a good idea to pass an instance in mailer action if you're using delay, so maybe its better to change the code above to :
Notifier.delay.password_reset(user.id)

Email body not sending with Action Mailer and Resque

I'm queuing up emails using ActionMailer and Resque. I'm not that familiar with either, but I have the basics down. Right now I'm debugging why when enqueueing emails, the email is sent but there is no body.. Mystifying.
When I execute ArtistReminderWorker.perform(8,2) the email is sent with the body. Great success!
When I execute Resque.enqueue(ArtistReminderWorker, 8, 2) the email is sent without the body. Mystery.
I thought these two statements were functionally the same, no?
At first I thought that the Resque worker wasn't able to look up the record, so I moved the database lookup from the worker into the mailer right before the mail command.
Here are my Worker and Mailer classes.
artist_reminder_worker.rb
class ArtistReminderWorker
#queue = :artist_reminders_queue
def self.perform(event_id, user_id)
ArtistReminderMailer.artist_reminder_email(event_id, user_id).deliver
end
end
artist_reminder_mailer.rb
class ArtistReminderMailer < ActionMailer::Base
default from: 'no-reply#example.com'
def artist_reminder_email(event_id, user_id)
#user = User.find(user_id)
#event = Event.find(event_id)
#url = dashboard_url
subject = "You have an upcoming gig at #{#event.venue.name}"
mail(to: #user.email, subject: subject)
end
end
My views are:
views/artist_reminder_mailer/artist_reminder_email.slim views/artist_reminder_mailer/artist_reminder_email.text.slim
Let me know if I can provide any more detail, thanks in advance!
Solved it. It wasn't actually anything I was doing wrong, but rather that I hadn't restarted the worker rake task in a while.
I guess when updating the Worker classes, you have to restart the worker.. Makes sense.

How to send emails with multiple, dynamic smtp using Actionmailer/Ruby on Rails

I saw this post but mine is slightly different:
Rails ActionMailer with multiple SMTP servers
I am allowing the users to send mail using their own SMTP credentials so it actually does come from them.
But they will be sent from the Rails app, so that means for each user I need to send their emails using their own SMTP server.
How can I do that?
Doing what is described in the other answer is not safe; you are setting class variables here, not instanced variables. If your Rails container is forking, you can do this, but now your application is depending on an implementation detail of the container. If you're not forking a new Ruby process, then you can have a race condition here.
You should have a model that is extending ActionMailer::Base, and when you call a method, it will return a Mail::Message object. That is your instance object and is where you should change your settings. The settings are also just a hash, so you can inline it.
msg = MyMailer.some_message
msg.delivery_method.settings.merge!(#user.mail_settings)
msg.deliver
Where in the above mail_settings returns some hash with appropriate keys IE
{:user_name=>username, :password=>password}
Here is a solution that I came up with based on the previous answers and comments. This uses an ActionMailer interceptor class.
class UserMailer < ActionMailer::Base
default from: proc{ #user.mail_settings[:from_address] }
class DynamicSettingsInterceptor
def self.delivering_email(message)
message.delivery_method.settings.merge!(#user.mail_settings)
end
end
register_interceptor DynamicSettingsInterceptor
end
For Rails 3.2.x
You can include AbstractController::Callbacks in your mailer class and the do a "after_filter :set_delivery_options" inside the mailer.
The set_delivery_options method would have access to instance variables setup by you in your mailer action and you can access the mail object as "message".
class MyNailer < ActionMailer::Base
include AbstractController::Callbacks
after_filter :set_delivery_options
def nail_it(user)
#user = user
mail :subject => "you nailed it"
end
private
def set_delivery_options
message.delivery_method.settings.merge!(#user.company.smtp_creds)
end
end
Since Rails 4+ it works to give the credentials directly via the delivery_method_options parameter:
class UserMailer < ApplicationMailer
def welcome_email
#user = params[:user]
#url = user_url(#user)
delivery_options = { user_name: params[:company].smtp_user,
password: params[:company].smtp_password,
address: params[:company].smtp_host }
mail(to: #user.email,
subject: "Please see the Terms and Conditions attached",
delivery_method_options: delivery_options)
end
end
See Sending Emails with Dynamic Delivery Options (Rails Guides)
Just set the ActionMailer::Base configuration values before each send action.
smtp_config = user.smtp_configuration
ActionMailer::Base.username = smtp_config.username
ActionMailer::Base.password = smtp_config.password
ActionMailer::Base.server = ..
ActionMailer::Base.port = ..
ActionMailer::Base.authentication = ..
in case somebody needs to set delivery method dynamically together with smtp credentials, u can use Mail::Message instance method to set delivery method together with it's variables so my addapted Aditya Sanghi version is:
class MainMailer < ActionMailer::Base
WHATEVER_CONDITION = true # example only f.e. #ser
include AbstractController::Callbacks
after_filter :set_delivery_options
private
def set_delivery_options
settings = {
:address => 'smtp.mandrillapp.com', # intentionally
:port => 587, # intentionally
:domain => 'your_domain', #insetad of localhost.localdomain'
:user_name => 'smtp_username',
:password => 'smtp_password',
:authentication => 'PLAIN' # or smthing else
}
if WHATEVER_CONDITION
message.delivery_method(:smtp, settings)
end
end
end

Can I specify a different recipient for an ActionMailer email based on the environment?

I'm wondering if it's possible to configure a Rails email derived from ActionMailer to send to a different recipient based on the environment. For example, for development I'd like it to send mail to my personal email so I don't clog up our company email account with "Testing" emails; for production however I want it to use the real address.
How can I achieve this?
The mail_safe plugin might be a little over kill.
A simple initializer will do
Rails 2.x
if Rails.env == 'development'
class ActionMailer::Base
def create_mail_with_overriding_recipients
mail = create_mail_without_overriding_recipients
mail.to = "mail#example.com"
mail
end
alias_method_chain :create_mail, :overriding_recipients
end
end
Rails 3.x
if Rails.env == 'development'
class OverrideMailReciptient
def self.delivering_email(mail)
mail.to = "mail#example.com"
end
end
ActionMailer::Base.register_interceptor(OverrideMailReciptient)
end
By default the development environment isn't setup to actually send emails (it just logs them).
Setting up alternate accounts can be done in many different ways. You can either use some logic in your mailer like so...
recipients (Rails.env.production? ? "email#company.com" : "test#non-company.org")
Or you can define the recipient as a constant in the environment files like so:
/config/environment/production.rb
EMAIL_RECIPIENT = "email#company.com"
/config/environment/development.rb
EMAIL_RECIPIENT = "test#non-company.org"
and then use the constant in your mailer. example:
recipients EMAIL_RECIPIENT
Also, there are several plugins that do this. The best one that I've found of the three I looked at was mail_safe.

Resources