I would like to turn off certain emails in development and on test/staging servers, but keep sending them in production. What I currently do is set the default "to" address for these administrative mails to blank:
default :to => Rails.env.production? ? "admin-list#sharethevisit.com" : ""
However, this puts stack traces in my development log when I'm testing the user mails which should be sending.
Is there a more effective way of disabling certain emails based on the current environment? I tried checking in the functions themselves, but is not ideal because I have to change each function, plus it doesn't actually work... it just fails to set the needed #account variable before rendering the email and creating a different stack trace.
def user_registered_notify_email(account)
if Rails.env.production?
#account = account
mail(:subject => "New user registered: #{#account.full_name}")
end
end
I usually use mail interceptors as described here: http://asciicasts.com/episodes/206-action-mailer-in-rails-3
I can't remember where I found this to credit the author but this is how I redirected email in development mode. Create a new file in RAILS_ROOT/config/initializers for this. DEFAULT_DEV_EMAIL_OVERRIDE is defined in our main site config with other static values.
if Rails.env.development?
if Rails.version =~ /^2\./
class ActionMailer::Base
def create_mail_with_overriding_recipients
mail = create_mail_without_overriding_recipients
mail.to = DEFAULT_DEV_EMAIL_OVERRIDE
mail
end
alias_method_chain :create_mail, :overriding_recipients
end
elsif Rails.version =~ /^3\./
if Rails.env.development?
class OverrideMailRecipient
def self.delivering_email(mail)
mail.to = DEFAULT_DEV_EMAIL_OVERRIDE
end
end
ActionMailer::Base.register_interceptor(OverrideMailRecipient)
end
end
end
Consider using an interceptor to set Mail::Message#perform_deliveries to false:
class NeverDeliverInterceptor
def self.delivering_email(message)
message.perform_deliveries = false
end
end
if !Rails.env.production?
ActionMailer::Base.register_interceptor(NeverDeliverInterceptor)
end
See API doc for source & other usage.
Related
I have a Sinatra app that's mounted on a Rails app under /admin. The Sinatra app is an admin dashboard, and therefore should only be available to authorized users.
To enforce that, I built a piece of Rack Middleware that will run before the Sinatra app is called.
The logic is simple -
If user is authenticated, continue as normal
If user is not authenticated, redirect to the root path with a flash alert message (I'm using the rack-flash gem to allow access to the flash messages in Rack)
Code below. I feel like I'm missing something in the redirect method. The Rack::Builder block constructs a mini-Rack application and the block inside further creates another Rack application (the Proc) that builds the Redirect Response with flash message.
When I run it, I get undefined method 'detect' for nil:NilClass, which indicates that neither block is returning a valid non-nil response. Do I need to run call somewhere on one of these blocks?
I'm using a Puma Webserver if that helps.
Thanks!
require "rack"
require "rack-flash"
class AdminAuthorizer
def initialize(app)
#app = app
end
def call(env)
#env = env
id = #env["rack.session"][:user_id]
user = User.where(id: id).first
# Check if user is authorized, otherwise redirect
user.admin? ? ok : redirect
end
private
def ok
#app.call(#env)
end
def redirect
Rack::Builder.new do
use Rack::Flash, sweep: true, accessorize: true
run(
Proc.new do |env|
env["x-rack.flash"].alert = "Insufficient permissions"
res = Rack::Response.new
res.redirect("/")
res.finish
end
)
end
end
end
Ok, figured it out myself for anyone else that's curious.
I had to use the env key 'action_dispatch.request.flash_hash', which is used by the Flash middelware here
I didn't have to use the rack-flash gem, although I'm sure that's still useful when building Sinatra apps and such
NOTE: This is on Rails v4.2.4. I believe there have been several changes to that Flash module since, so I don't know if that key has changed. But you can confirm by searching the latest repo for a similar definition.
require "rack"
class AdminAuthorizer
FLASH = ActionDispatch::Flash
def initialize(app)
#app = app
end
def call(env)
#env = env
id = #env["rack.session"][:user_id]
user = User.where(id: id).first
# Check if user is authorized, otherwise redirect
user.admin? ? ok : redirect
end
private
def ok
#app.call(#env)
end
def redirect
# Calls a Rack application (the defined Proc). If you want to do more steps
# or get fancier, you can wrap this in a Rack::Builder call
#
# Rack::Builder.app(redirect_proc)
# use (blah)
# run (blah)
# end.call(#env)
#
redirect_proc.call(#env)
end
def redirect_proc
Proc.new do |env|
# Use the key 'action_dispatch.request.flash_hash' to set
# an new FlashHash object. This will overrite any existing FlashHash
# object, so use it carefully. You can probably avoid that by checking
# for an existing one and adding to it.
env[FLASH::KEY] = FLASH::FlashHash.new(alert: "Insufficient permissions")
# Construct the redirect response
res = Rack::Response.new
res.redirect("/")
res.finish
end
end
end
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
I upgraded my app from 3.0 to 3.1 to 3.2 (per the recommendations). All specs and scenarios pass and they test the email sent pretty deeply. What happens is that the email appears to be sent if I look at my maillog and the correct templates appear to be rendered, but no mail is actually delivered to the user.
This code worked perfectly in the 3.0 version of this app. I'm hoping someone can spot out what I'm missing. The first several files I've shown in the code below are configuration files. Again, this stuff worked fine in previous deploys. I'm baffled.
The "controller mixin" is a module that is mixed into any controller that wants to send mail. The controllers do almost exactly the same thing so all I have to change is the mailers and the views.
I'm stumped how to proceed even debugging this. I know I can specify more detailed smtp settings but this worked fine before and it is resilient once I prop this in DNS, not relying on a particular hostname.
Any thoughts are most appreciated!
production.rb
# other non-mailer stuff elided.
config.action_mailer.default_url_options = { :host => `hostname` }
ActionMailer::Base.default(:charset => 'utf-8')
ActionMailer::Base.perform_deliveries = true
config/initializers/setup_mail.rb
require 'development_mail_interceptor'
Mail.register_interceptor(DevelopmentMailInterceptor) if Rails.env.development?
lib/development_mail_interceptor.rb
class DevelopmentMailInterceptor
def self.delivering_email(message)
message.subject = "[#{message.to}] #{message.subject}"
message.to = "my.address#gmail.com"
end
end
Controller mixin (this is in a module that is mixed into controllers that do mail):
def send_detail_email
#detail_email = #mailer_klass.sent(#ar_obj, session)
end
def send_confirm_email
#confirm_email = #mailer_klass.confirm(#ar_obj, session)
end
def send_email
send_detail_email
# Field naming is inconsistent, so sniff out the contact type
contact_type = ''
contact_type = #ar_obj.contact if #ar_obj.respond_to?(:contact)
contact_type = #ar_obj.best_contact_method if #ar_obj.respond_to?(:best_contact_method)
contact_type = #ar_obj.contact_method if #ar_obj.respond_to?(:contact_method)
# If no contact type, then it's probably assumed a user object's email
contact_type = 'email' if #ar_obj.respond_to?(:user) &&
!#ar_obj.respond_to?(:contact) &&
!#ar_obj.respond_to?(:best_contact_method) &&
!#ar_obj.respond_to?(:contact_method)
send_confirm_email if contact_type == 'email'
end
for non-production rails 2.x environments i want to block/filter any outgoing emails that aren't addressed to people in my organization (e.g. "*#where-i-work.com").
please note, i don't want to block email entirely - i know i can just write them to the logs in test mode - i need emails to internal employees to be delivered.
thanks.
You could try extending the Mail::Message.deliver function in your environment.rb file - something like (not tested - just demo code!):
class Mail::Message
def deliver_with_recipient_filter
self.to = self.to.to_a.delete_if {|to| !(to =~ /.*#where-i-work.com\Z/)} if RAILS_ENV != production
self.deliver_without_recipient_filter unless self.to.blank?
end
alias_method_chain :deliver, :recipient_filter
end
Note that this id for Rails 3 - I think all versions of Rails 2 use TMail instead of Mail, so you'll need to override something else if you're not using Rails 3.
Hope this helps!
based on #Xavier's rails 3 proposal i was able to get it working in rails 2:
class ActionMailer::Base
def deliver_with_recipient_filter!(mail = #mail)
unless 'production' == Rails.env
mail.to = mail.to.to_a.delete_if do |to|
!to.ends_with?('where-i-work.com')
end
end
unless mail.to.blank?
deliver_without_recipient_filter!(mail)
end
end
alias_method_chain 'deliver!'.to_sym, :recipient_filter
end
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.