multiple_mailers - send emails by different smtp accounts

I use gmail to send email notifications on my website, it's really easy to build based on actionmailer

ActionMailer::Base.smtp_settings = {
  :address => 'smtp.gmail.com',
  :port => 587,
  :domain => 'railsbp.com',
  :authentication => :plain,
  :user_name => 'notification@railsbp.com',
  :password => 'password'
}

But I found it does not allow to setup 2 different smtp accounts, e.g. I want to send notification email with notification@railsbp.com and send exception notifier email with exception.notifier@railsbp.com, after googling, I hacked my mailer classes with

class NotificationMailer < ActionMailer::Base
  if Rails.env.production?
    class <<self
      def smtp_settings
        options = YAML.load_file("#{Rails.root}/config/mailers.yml")[Rails.env]['exception_notifier']
        @@smtp_settings = {
          :address              => options["address"],
          :port                 => options["port"],
          :domain               => options["domain"],
          :authentication       => options["authentication"],
          :user_name            => options["user_name"],
          :password             => options["password"]
        }
      end
    end
  end
end

then add a new config file config/mailers.yml

production:
  common: &common
    address: 'smtp.gmail.com'
    port: 587
    domain: 'rails-bestpractices.com'
    authentication: 'plain'

  notification:
    <<: *common
    user_name: 'notification@rails-bestpractices.com'
    password: 'password'

  exception.notifier:
    <<: *common
    user_name: 'exception.notifier@rails-bestpractices.com'
    password: 'password'

that allows me to setup one smtp account per actionmailer class, keep in mind that you should only hack smtp_settings for what environment you really want to send emails (here is production), if you don't check Rails.env, it will send email even in development and test environments.

Now it works fine, I can send emails by as many smtp accounts as I like, but it looks ugly, I don't like hacking codes all over my mailer classes. So I abstract it to a new gem multiple_mailers, like the hack above, you should define config file config/mailers.yml and for each mail class, what you only need is to declare its mailer account name

class NotificationMailer < ActionMailer::Base
  mailer_account "notification"
end

class ExceptionNotifier
  class Notifier < ActionMailer::Base
    mailer_account "exception.notifier"
  end
end

Posted in  rails actionmailer


passenger with http_gzip_static_module

Rails 3.1 has been released for a long time, asset pipeline becomes more and more popular, I also upgraded my rails website.

I used nginx + passenger for my rails projects, but nginx only supports dynamic gzip support (compress in runtime), there is a http_gzip_static_module for nginx, which can make full use of rails asset pipeline.

I don't like the way to customize my Nginx installation during passenger installation, I found there is a pull request to add http_gzip_static_module, so I changed to source code of passenger gem, then installed nginx as default. :-)

Posted in  rails passenger


rake arguments

Long ago I began to write some rake tasks, it's simple but doesn't have an instruction about how to add arguments to a rake task. What I did before is to use ruby environment variables.

task :try_argument do
  ENV['GLOBAL_ARGUMENT1'] or ENV['GLOBAL_ARGUMENT2']
end

GLOBAL_ARGUMENT1=xxx GLOBAL_ARGUMENT2=yyy rake try_argument

As you seen, I have to set the global environment variable to pass the arguement to a rake task.

But there is another way to pass the arguments to rake task via []

task :try_argument, [:key1, :key2] do |t, args|
  args.with_defaults(:key1 => value1, :key2 => value2)
  args[:key1] or args[:key2]
end

rake try_argument[xxx, yyy]

and if there is dependent task, you should define it like

task :try_argument, [:key1, :key2] => :environment do |t, args|
  args.with_defaults(:key1 => value1, :key2 => value2)
  args[:key1] or args[:key2]
end

rake try_argument[xxx, yyy]

It looks like the difference between hash arguments and normal arguments.

Both of them have disadvantage:

ENV arguments also changes the system env variables normal arguments do not make sense when calling, difficult to remember the meanings of arguments.

Both work fine, it depends on you to use which one.

Posted in  rake


passenger with redis

Today I encountered an issue that passenger forks too many workers than what we set (6) on qa servers. I used strace, the passenger worker is blocked by failed to writing to a socket, like

select(15, [], [13], [], [58, 915000])

fd 13 is a socket.

I also tried netstat and found the status for some redis socket connections are CLOSE_WAIT.

So I judged this is the problem the ruby redis clients are not closed correctly. This reminds me that passenger fork() nature, I checked our source codes, unfortunately, we didn't do anything special for passenger fork.

This is the link tells you how to close the redis connection after passenger forks a worker. After deploy the new codes to qa servers, passenger never forks more workers than we expected. But the workers still hang up according strace result, that means some workers keep inactive status, they won't be able to handle any requests. Wooops...

I looked through the redis-rb source codes, we used redis 2.0.5, it didn't handle TIMEOUT error and always retry writing to redis. Fortunately, the latest redis version is 2.2.2 and it already fixed this issue, retry 3 times, if still failed, the release the connection.

Now it works fine, no unexpected additional passenger workers and no unexpected inactive workers.

Posted in  passenger redis


avoid committing git conflicts

I made a mistake when merging branch last week, I forgot to remove a conflict syntax "<<<<<< HEAD" and push it to remote repository. It breaks other one's development. So stupid to make such mistake.

To avoid making such mistake anymore, I write a git hook .git/hooks/pre-commit to check conflict syntax "<<<<<<" and ">>>>>>"

#!/usr/bin/env ruby

`git diff-index --name-status HEAD`.split("\n").each do
|status_with_filename|
  status, filename = status_with_filename.split(/\s+/)
  next if status == 'D'
  File.open(filename) do |file|
    while line = file.gets
      if line.include?("<<<<<<<") || line.include?(">>>>>>>")
        puts "ERROR: #{filename} is conflict"
        exit(1)
      end
    end
  end
end

It will prevent you from committing conflicts.

Posted in  git


Fork me on GitHub