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"
endSend 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
endFree 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.
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
endWhat 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.