Defining a Custom Delivery Method for Action Mailer


Ruby Rails

The other day I had a chance to create a custom delivery method for Action Mailer. I have taken over a Rails project from a vendor and now I have been trying to fix all issues they left. Since they didn’t leave any documents or even logs of the repository, it’s a little mess.

While the former team was using Rails, they weren’t using Action Mailer for mail deliveries. Instead, the app was calling their proprietary REST API to send mails. I don’t know why and there is no information about that decision. Anyway, I had to change the backend of the mail delivery from the old one to a managed service like SendGrid or AWS SES because the API was going to shut down in a couple of months. To that end, I wanted to replace the existing implementation with Action Mailer because we didn’t want to implement the details like talking SMTP/HTTP and we want to use Rails’ template system.

By using Action Mailer, we don’t have to care about the backend implementation of mail delivery. Dealing with the Action Mailer’s interface, we can concentrate on more valuable things like business and user interface. Besides, since our team hadn’t decided which mail delivery service to use yet, this encapsulation helped us. We didn’t even have to have the backend implementation when creating mailers. Thanks to that, we were able to split a massive task into small ones.

As I mentioned, we hadn’t decided on the new mail delivery service for the app. This means our team still had to use the old mail server for a while. But it was not a problem because Action Mailer hides the details of the backend implementation for us. However, the existing mail server didn’t let us use SMTP and we need to call its REST API. It was a problem because Action Mailer wouldn’t know about the proprietary REST API. But still, it was a piece of cake. All we had to do was to add a custom delivery method to tell Action Mailer how to deal with the REST API. And when we get the SMTP account for the new mail delivery service in the near future, then we can replace the custom delivery method with the ordinary :smtp delivery method.

Let’s take a look at how to create a custom delivery method. Surprisingly, all methods Action Mailer requires are just .new(values) and #deliver!(mail).

# Defining a custom delivery method.
class MyCustomDeliveryMethod
  def initialize(value)
    @config = value
  end

  def deliver!(mail)
    # TODO: replace with the proper implementation.
    puts "You can send the mail to #{mail.to} with the @config (like '#{@config.foo}')."
  end
end

# Registering the custom delivery method to Action Mailer.
ActionMailer::Base.add_delivery_method :my_custom_delivery_method, foo: 'bar'

That’s it. A delivery method can be initialized with its argument when registering it to Action Mailer. In this example, foo: 'bar' will be passed as the argument of the #initialize method. When the application is going to deliver a mail with it, the #deliver method will be invoked with the mail argument. This is where to place its implementation of mail delivery. You can do whatever you need like writing to files, making HTTP requests, connecting to SMTP servers, or so forth.

You might think it’s too simple and you might want to see the real implementation examples. You can see the built-in delivery method like :smtp or :test in the rails/rails repository. Actually, these are implemented in the mail gem as follows and registered to Action Mailer here.