Typical Rails convention will tell you to send email like using the ActiveMailer API like so. This is not a very good idea, and let me tell you why.

NotificationMailer.notification(@user, @post).deliver_later

Sample implementation

This kind of mailer would typically be implemented like so:

class NotificationMailer < ApplicationMailer
  def notification(user, post)
    @user = user
    @post = post
    
    mail to: @user.email,
         subject: "You've got a notification"
  end
end

Race condition!

If you follow Rails best practices, you’d be using #deliver_later to send email in the background. This is great for performance, but that means you have to start thinking asynchronously. The code above is written in a very synchronous style and will fail in certain conditions. Consider this scenario:

  • A notification is triggered for user Joe, who is [email protected], by creating a new Post.
  • A background job is created.
  • Before an email could be sent, the Post is deleted.
  • The mailer now fails because @post is nil.

Think asynchronously

Instead of passing Rails models or record ID’s, pass a fully-loaded plain hash of everything the mailer needs to send the email. Consider something like this instead:

NotificationMailer.notification(
  user: {
    email: @user.email
  },
  post: {
    id: @post.id,
    title: @post.title,
    url: post_url(@post)
  }
).deliver_later

The mailer will now not need to hit the database to fetch any data, eliminating any race conditions. Even if the post is modified or deleted later on, the email will still send just fine.