Adding custom HTTP mailer delivery method in Rails

Adding custom HTTP mailer delivery method in Rails

Problem

Railway prevents SMTP outbound connections in the hobby plan which prevents me from using an SMTP service to send emails from my application.

I'm using ZeptoMail from Zoho. Thus, I had to use the HTTP API from ZeptoMail for sending my emails. However, ZeptoMail did not have a Ruby SDK. So, I decided to implement a custom ActionMailer delivery method.

How ActionMailer works

Before building the class, it helps to understand how ActionMailer handles delivery.

ActionMailer is a Rails abstraction for sending emails. It gives you mailer classes, view templates, and delivery methods.

You can register any class as the delivery method with ActionMailer::Base.add_delivery_method. The only requirement for this class is that its constructor should accept a settings hash and implement deliver!(mail).

The mail object is an instance of Mail::Message. It has information such as mail[:from].addresses, mail.subject, mail.html_part, mail.text_part.

Building the delivery class

We have to create a plain Ruby object in the app/lib directory. We'll use Net::HTTP to send the request.

# app/lib/zeptomail_delivery.rb
class ZeptomailDelivery
  attr_accessor :settings

  def initialize(settings)
    @settings = settings
  end

  def deliver!(mail)
    uri = URI("https://api.zeptomail.in/v1.1/email")

    request = Net::HTTP::Post.new(uri)
    request["Content-Type"]  = "application/json"
    request["Accept"]        = "application/json"
    request["Authorization"] = settings[:api_token]
    request.body = {
      from:     { address: mail.from.first },
      to:       mail.to.map { |addr| { email_address: { address: addr } } },
      subject:  mail.subject,
      htmlbody: mail.html_part&.body&.decoded,
      textbody: mail.text_part&.body&.decoded
    }.compact.to_json

    Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
      http.request(request)
    end
  end
end

Make sure to build a JSON body that matches the expected request format of your mail service's HTTP API.

Register the class

Now that we have the class ready, let us tell Rails that we have a new delivery method. Add the config for action_mailer in production.rb

# config/environments/production.rb
require Rails.root.join("app/lib/zeptomail_delivery")

# ..existing configs

# ZeptoMail HTTP API configuration
ActionMailer::Base.add_delivery_method :zeptomail, ZeptomailDelivery
config.action_mailer.delivery_method = :zeptomail
config.action_mailer.zeptomail_settings = {
  api_token: ENV.fetch("ZEPTOMAIL_API_TOKEN")
}

# ..existing configs

Note that the api_token we pass here will be accessible assettings[:api_token inside the ZeptomailDelivery class.

Conclusion

That is how you add a custom delivery method for ActionMailer.