OquMail
Loading account…
Email API2 min readUpdated

Send Email from Ruby on Rails: ActionMailer SMTP or the API in a Job

Send email from Rails on your own domain: ActionMailer smtp_settings for port 587 with credentials, deliver_later, and an API client called from an ActiveJob.

ActionMailer is already wired into Devise, password resets and every mailer you have written, so the fastest route to sending from your own domain is to point its SMTP settings at a mailbox on that domain. The HTTPS API is the better fit for product events where you want the returned id in your database and no SMTP connection in the job. Both are shown, with the credentials kept in Rails encrypted credentials and the sends pushed through ActiveJob.

Quick answer

In config/environments/production.rb set delivery_method :SMTP (the standard way computers hand off outgoing email) with address mail.oqumail.com, port 587, authentication :plain, enable_starttls_auto true, and the mailbox login from credentials. Call mailers with deliver_later. For the API, a small Net::HTTP client in app/services posts to https://api.oqumail.com/api/v1/emails from an ActiveJob.

Option 1: ActionMailer over SMTP

Store the login with bin/rails credentials:edit as oqumail: { smtp_user: "app@yourdomain.com", smtp_password: "..." }. Then:

# config/environments/production.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
config.action_mailer.default_url_options = { host: "app.yourdomain.com", protocol: "https" }
config.action_mailer.smtp_settings = {
  address:              "mail.oqumail.com",
  port:                 587,
  user_name:            Rails.application.credentials.dig(:oqumail, :smtp_user),
  password:             Rails.application.credentials.dig(:oqumail, :smtp_password),
  authentication:       :plain,
  enable_starttls_auto: true,   # STARTTLS on 587; do not set tls: true (that is port 465)
  open_timeout:         10,
  read_timeout:         10
}

# app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "Acme <app@yourdomain.com>"
  layout "mailer"
end

Send it from a job

# anywhere
UserMailer.with(user: user).welcome.deliver_later

# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  def welcome
    @user = params[:user]
    mail(to: @user.email, subject: "Welcome to Acme")
  end
end

Free business email on your own domain

OquMail gives you up to 15 mailboxes on your domain — free — with guided SPF/DKIM/DMARC, webmail, IMAP/SMTP for any mail app, and a send API. Setup is a few DNS records; changes usually take effect within minutes, occasionally up to 48 hours. Start at oqumail.com.

Get started free

Option 2: the API from an ActiveJob

Add oqumail: { api_key: "oqm_live_..." } to credentials. The service returns the receipt so the job can store the id against the user or order:

# app/services/oqumail_client.rb
require "net/http"
require "json"

class OqumailClient
  ENDPOINT = URI("https://api.oqumail.com/api/v1/emails")

  class RetryLater < StandardError; end

  def self.send_email(to:, subject:, html: nil, text: nil, from_name: nil)
    body = { to: Array(to), subject: subject, html: html, text: text, fromName: from_name }.compact
    req = Net::HTTP::Post.new(ENDPOINT, {
      "Authorization" => "Bearer #{Rails.application.credentials.dig(:oqumail, :api_key)}",
      "Content-Type"  => "application/json"
    })
    req.body = body.to_json

    res = Net::HTTP.start(ENDPOINT.host, ENDPOINT.port, use_ssl: true, open_timeout: 10, read_timeout: 10) do |http|
      http.request(req)
    end

    case res.code.to_i
    when 202 then JSON.parse(res.body)            # {"id"=>..., "messageId"=>..., "status"=>"queued"}
    when 403, 429 then raise RetryLater, res.body
    else raise "OquMail send failed #{res.code}: #{res.body}"
    end
  end
end

# app/jobs/order_confirmation_job.rb
class OrderConfirmationJob < ApplicationJob
  queue_as :mailers
  retry_on OqumailClient::RetryLater, wait: :polynomially_longer, attempts: 5

  def perform(order)
    return if order.confirmation_message_id.present?   # idempotent
    receipt = OqumailClient.send_email(
      to: order.email,
      subject: "Order #{order.number} confirmed",
      html: ApplicationController.render(template: "orders/confirmation", assigns: { order: order }, layout: "mailer")
    )
    order.update!(confirmation_message_id: receipt["id"])
  end
end

What the API answers

  • 202 Accepted: the message is queued. The JSON carries id, messageId and status; store the id so a support question can be matched to the delivery log.
  • 400: the body failed validation, usually to missing or not an array, or neither html nor text present.
  • 401: the key is missing, revoked or mistyped.
  • 403: the mailbox is suspended, has reached its daily limit of 250, or sent more than 10 messages in a minute ("Sending too fast"). Back off; do not retry in a tight loop.
  • 422: the message was blocked by content checks; the response explains why. Do not retry unchanged.
  • 429: requests arrived faster than the per-key limit. Honour the Retry-After header.

Common questions

Net::ReadTimeout or "530 Must issue a STARTTLS command first"?

The first is the host blocking outbound SMTP (switch to the API, which uses 443). The second means enable_starttls_auto is false or the port is wrong; port 587 with STARTTLS is the pair.

Does the API take a Reply-To?

No. If a reply should go to a person, send from that person's mailbox (a key per mailbox) or write the contact address in the body. ActionMailer over SMTP accepts reply_to as usual.

How do I test without sending?

Keep delivery_method :test in test.rb and use ActionMailer::Base.deliveries; stub OqumailClient in specs. The staging guide covers safe real sends.