Rails Email Deliverability Tips ‘n’ Tricks with Mailgun

Even if you’re hella legit about sourcing your email list, sometimes addresses expire, people close accounts, or some other tragedy befalls otherwise healthy emails.

To make sure that you reputation doesn’t suffer you can use these simple tips and tricks!

Skip sending to bad addresses

Add an after_action to ActionMailer::Base that will not perform deliveries to emails that are undeliverable.

class ApplicationMailer < ActionMailer::Base
  after_action :check_email

  def check_email
    recipient = mail.to[0]
    mail.perform_deliveries = !Email.where(undeliverable: true).find_by_email(recipient)

In this example we have an Email model with email and undeliverable columns. We simply check that the recipient is deliverable after we’ve populated the mail object’s data, but before we actually send the email. We can manually update this table as we get information about undeliverable addresses and bounces from Mailgun.

Update bad Addresses

A great place to get your list of bounced addresses is straight from the Mailgun API

curl -s --user 'api:key-yourapikey' -G   https://api.mailgun.net/v3/your.domain/bounces?limit=10000 > bounces.json

Be sure to put in your actual api key and domain. I’ve set the limit here to 10k which is the maximum. If you have more than that you’ll need to page through.

Once you have this list you can update your emails table with the info.

Track Bounces as they Occur via Webhooks

Updating your table with any bounced addresses in bulk is a great way to prune out bad addresses and keep your reputation intact. Even better is update the table as they occur. Thankfully Mailgun provides a webhook to track bounces.

We can create a controller to receive the event, being sure to check that the signature matches.

class MailgunController < ApplicationController
  skip_before_action :verify_authenticity_token

  before_action :verify_mailgun_signature

  def bounced


  def verify_mailgun_signature
    api_key = ENV["MAILGUN_API_KEY"]
    token = params[:token]
    timestamp = params[:timestamp]
    signature = params[:signature]

    digest = OpenSSL::Digest::SHA256.new
    data = [timestamp, token].join

    valid_signature = signature == OpenSSL::HMAC.hexdigest(digest, api_key, data)

    unless valid_signature
      render nothing: true, status: :unauthorized

When we receive the webhook we verify the signature, return 406 unuathorized if it doesn’t match. If it does match then we can be sure that the request came from Mailgun so we can mark the address as undeliverable.


Hopefully this saves you some time when keeping your mailing list clean, peace!

Suppressing “NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index” when running rake test with postgres db

Recently I switched to Postgres for my Rails database needs. Everything is good, but when running tests I get pages of NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index ... spewing into my output.

I asked around and got some help from @robtreat2. A quick search lead me to the Postgres docs on the matter.

client_min_messages (string)
Controls which message levels are sent to the client. Valid values are DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, LOG, NOTICE, WARNING, ERROR, FATAL, and PANIC. Each level includes all the levels that follow it. The later the level, the fewer messages are sent. The default is NOTICE. Note that LOG has a different rank here than in log_min_messages.

That’s great for straight Postgres, but I don’t want to have some connection.exec jimmied into my test environment. Fortunately the Rails docs were also in that same search result:

:min_messages – An optional client min messages that is used in a SET client_min_messages TO <min_messages> call on the connection.

Using my newfound knowledge I was able to solve the issue by adding this to my database.yml file:

  min_messages: WARNING

Giving up on has_many_polymorphs bug after an hour

I remember reading about has_many_polymorphs a couple of years ago, then again last year. Each time around when I wanted some sort of polymorphic has_many :through. Each time I figured, “Eh, it’s just another couple of tables” or “I can just map them in a method in the model, there’s not that much data”. But this time I finally gave it a try.

First I got this error when using the gem with Rails 3: has_many_polymorphs/support_methods.rb:69:in `warn': wrong number of arguments (1 for 3) (ArgumentError). So I looked at the github network graph and found a branch that seemed to fix that.

That worked, but next I got this: Could not find a valid class for :collections_items (tried CollectionsItem). If it's namespaced, be sure to specify it as :"module/collections_items" instead. (ActiveRecord::Associations::PolymorphicError) What are they trying to do, pluralize post positive adjectives? My model is named CollectionItem, which seems sensible to me.

This time I try adding a :through option to set it straight.

  has_many :collection_items
  has_many_polymorphs :items, :through => :collection_items, :from => [:sprites, :collections]

Now I get the following amazing error message:

has_many_polymorphs/class_methods.rb:441:in `create_has_many_through_associations_for_children_to_parent': You can't have a self-referential polymorphic has_many :through without renaming the non-polymorphic foreign key in the join model. (ActiveRecord::Associations::PolymorphicError)

This probably makes sense to everyone except me, but I’ve got to work with the skills I’m given.

What I’d really like is for polymorphic has_many :through to ‘just work'(TM).

Something kind of EXACTLY LIKE this:

class CollectionItem < ActiveRecord::Base
  belongs_to :collection
  belongs_to :item, :polymorphic => true
class Collection < ActiveRecord::Base
  belongs_to :user
  has_many :collection_items
  has_many :items, :through => :collection_items

Why is that so hard? No one will ever know…

So I did what I always do:

class Collection < ActiveRecord::Base
  belongs_to :user
  has_many :collection_items
  def items

After all, there’s not that much data anyway…

Random Scope

Here’s a little module that will make adding things like Cards.random(4) a breeze!

# This module takes care of adding a random scope to records
module RandomScope
  def self.included(model)
    model.class_eval do
      if connection.adapter_name == "MySQL"
        named_scope :random, lambda { |amount|
            {:order => "RAND()", :limit => amount}
            {:order => "RAND()"}
        named_scope :random, lambda { |amount|
            {:order => "RANDOM()", :limit => amount}
            {:order => "RANDOM()"}

Pluralizing Post-positive Adjectives in Rails

You know those adjectives that come after nouns? Well, sometimes you may want to pluralize them correctly in your Rails App. I might have been able to get by with “work_in_processes”, but “bill_of_materialsses” just wasn’t going to cut it.

Here’s the technique I used:

# Add new inflection rules using the following format:
ActiveSupport::Inflector.inflections do |inflect|
  inflect.irregular 'BillOfMaterials', 'BillsOfMaterials'
  inflect.irregular 'bill_of_materials', 'bills_of_materials'
  inflect.irregular 'WorkInProcess', 'WorksInProcess'
  inflect.irregular 'work_in_process', 'works_in_process'

If you have any improvements or suggestions please comment!

Now off to Burger King to order two Whoppers Jr.

Can’t dup NilClass… maybe try `unloadable`

If you’re using a model or controller from an engine in Rails 2.3.2 you may encounter some crazy errors from time to time. Errors like: A copy of ApplicationController has been removed from the module tree but is still active! Or sometimes the even weirder one: can't dup NilClass

In one situation where you get A copy of ApplicationController has been removed from the module tree but is still active! it could be because you’re using a plugin or engine that provides controllers and inherits from ApplicationController. Some of the classes inherited or included in your engine controllers may fail to get unloaded and cause trouble after the first request to your system. Add unloadable inside your controller classes in your engine.

The can't dup NilClass error really tricked me though. It seemed to be saying something about duping nil class but not really. It was a lie! Well, almost. See, I had a model in my engine like this:

class Account < ActiveRecord::Base
  include Authentication::ByCookieToken
  include Authentication::AccountBuddy
  has_many :logins, :dependent => :destroy
  attr_accessible :nickname, :email

And I had a module in my app (not in the engine) like this (sort of a reverse-micro-plugin):

module Authentication
  module AccountBuddy
    def self.included(account)
      account.class_eval do
        has_many :characters

And when I tried to access account.characters it was all “can’t dup NilClass”. But what it really meant to say was: “I’m returning nil instead of an array containing the characters belonging to this account because I became confused during the loading and unloading of all your crazy ass models. -Love, Rails”

So adding a little unloadable also fixes that.

class Account < ActiveRecord::Base
  unloadable # <= That's the ticket!
  include Authentication::ByCookieToken
  include Authentication::AccountBuddy # reverse-micro-plugin 
  has_many :logins, :dependent => :destroy
  attr_accessible :nickname, :email

What a load off.

A better way

Courtesy of Paul (from the comments)

unloadable is now deprecated, I think. As far as I am aware, the “right” way to do this is one of the following:

First, you could put something like this in your plugin’s init.rb file:

# This plugin should be reloaded in development mode.
if RAILS_ENV == 'development'
  ActiveSupport::Dependencies.load_once_paths.reject!{|x| x =~ /^#{Regexp.escape(File.dirname(__FILE__))}/}

Second, you could put something like this in your application’s environment.rb file:

config.reload_plugins = true if RAILS_ENV == ‘development’

If the above did not solve your problem:

Courtesy of Evan Owen (from the comments)

Another possible cause of this problem is that ActiveRecord also defines self.included. In order to ensure that ActiveRecord gets the call and can finish loading the model, a call to super needs to be added to the end of self.included like so:

def self.included(account)
  account.class_eval do
    has_many :characters
  super # fixes the "can’t dup nil" issue

This did not work in my specific case, but may be the correct solution if you are suffering from a similar but different problem.

Adding a Non-null Column with no Default Value in a Rails Migration

This is something that I’ve often needed to do: add a new column to the DB that has a non-null constraint, but also doesn’t have a default value. There are a some options:

  • Forget the DB constraint and use `validates_presence_of` in the model
  • Add a default value for the new column with non-null and then remove the default
  • Add the column without a default value, then alter it to be non-null

The first method of simply using `validates_presence_of` won’t cut it for me because that doesn’t actually make guarantees on the data stored in the DB from interfaces outside the of application.

Adding the new non-null column with a default value and then altering it to remove the default would probably be the best choice if you just need a standard value for all your historic data.

The third method is how I did it, and I like it best for any data that can be computed to a reasonable value to start out.Here’s the source for my migration:

class AddLoginMetricsToAccounts &lt; ActiveRecord::Migration
  def self.up
    add_column :accounts, :last_login, :datetime
    add_column :accounts, :total_logins, :integer, :null => false, :default => 1
    Account.all.each do |account|
      account.last_login = account.created_at
    change_column :accounts, :last_login, :datetime, :null => false
  def self.down
    remove_column :accounts, :total_logins
    remove_column :accounts, :last_login

I’m adding a last_login column that I want to be non-null, but because it has no default value most DBs won’t allow the new column to be added (it violates data integrity). So the thing is to add the column without a a non-null constraint, populate it with acceptable values, and then to change the column to include the constraint.

Hope this comes in handy!