Send Emails in Ruby: A Practical Guide

Using Ruby to send email: master every method from Net::SMTP to UniOne API
Denys Romanov Denys Romanov 02 july 2025, 11:33 76
For experts

When you send emails in Ruby, you expect the message to land in the inbox a second later. Simple, right? Yet behind that single call sits a hidden maze of handshakes, encryption switches, throttling limits, and client quirks that can turn a friendly “hello” into a bounce – or worse, a silent drop. Let’s walk through that maze together. There will be questions you normally discover only at 2 a.m., like “Why did Gmail block my PDF?” or “Why did my cron job freeze the rails server?”, and we’ll try to touch them one by one while we code. By the end, hitting send will feel as certain as printing to the console – just a lot more useful.

SMTP vs Email API in Ruby

Before we dive into the code, pause for a moment and picture two doors. Behind the first stands a plain socket: you shake hands with the mail server, utter the age-old SMTP commands, and push every byte yourself. Behind the second is a web endpoint: you drop a JSON payload, step aside, and let a cloud pipeline finish the trip. Both doors deliver the same message, yet the journey, risk, and work you carry are very different. In the next two sections, we will show how sending emails in ruby can follow two very different paths and see which one makes more sense for your project’s size, security rules, and need for feedback.

How SMTP Works in Ruby

When you use SMTP, your Ruby code opens a direct TCP connection to the mail server and then issues the SMTP commands step by step.

  1. Handshake – Your script says EHLO (or HELO) with the local domain name. The server replies with a list of features it supports.

  2. Upgrade to STARTTLS – If the server offers encryption, you call smtp.enable_starttls_auto (STARTTLS). Alternatively, you call smtp.enable_tls (SMTPS) before smtp.start to enable implicit TLS. Ruby’s Net::SMTP handles the OpenSSL session and switches the channel to an encrypted one.

  3. Authentication – After the channel is secure, the client sends AUTH PLAIN or AUTH LOGIN with Base-64-encoded credentials.

  4. Envelope commandsMAIL FROM:<sender@domain> tells the server who is sending the message. RCPT TO:<user@example.com> lists each recipient.

  5. Message data – The DATA command starts the body transfer. Ruby streams the headers and body exactly as they will appear in the inbox.

  6. Quit – The QUIT command closes the session, freeing server resources.

Send emails in Ruby with SMTP - UniOne Blog

Direct SMTP is still handy when you:

  • need precise control over every MIME header;

  • run a small internal system that talks to a corporate relay on the same network;

  • must support legacy appliances that accept only SMTP.

Keep in mind that with SMTP, you take care of all the retry logic, bounce parsing, and security updates yourself.

How Email APIs Work and Scale

Email APIs replace the raw protocol with simple HTTPS requests:

  1. Your code builds a JSON payload – recipients list, subject, HTML or text parts, and attachment data.

  2. It POSTs this payload to the provider’s REST endpoint over encrypted HTTP.

  3. The provider prepares and queues the mail, performs all SMTP handshakes on its side, and returns a message ID.

  4. Mail events such as “delivered”, “bounced”, or “open” arrive later to your webhook URL, so your app can react in real time without polling.

Since the pipeline is owned by the provider, you automatically get:

  • Automatic retries – transient failures are retried with exponential back-off.

  • Metrics – dashboards track delivery rate, bounce types, and engagement.

  • Webhooks – push events let you update databases or trigger workflows at once.

  • Security – the service maintains the TLS libraries and enforces modern ciphers.

Send emails in Ruby with API - UniOne Blog

Quick Comparison

 

Direct SMTP

Email API

Setup effort

Very low (built-in library)

Low (HTTP client + key)

Control level

Full control over every detail

High-level payload, the provider handles low-level details

Scaling

Your app must manage queues, retries, etc.

Provider scales automatically

Event feedback

You must parse bounces

Webhooks with structured JSON

Best fit

On-prem scripts, closed networks

Cloud apps, high volume, real-time tracking

Tip: use SMTP when you need tight control or an offline relay. Choose an Email API when you value speed, observability, and built-in reliability.

Net::SMTP: The Lightweight Core Library

Think of Net::SMTP as the pocketknife that ships with every Ruby install. It looks tiny, yet it can open a full-size mail channel on its own. Just one standard module and the OpenSSL bundle is already sitting on your disk. Along the way, however, you may see a few road bumps – wrong ports, self-signed certs, malformed commands, that trip newcomers more often than the code itself.

Minimal Setup Example

The Ruby standard library already contains everything you need to send a simple email. No extra gems are required; OpenSSL ships with the default build.

require "net/smtp"

require "openssl"

 

FROM = "alice@example.com"

TO   = "bob@example.com"

SUBJ = "Hello from Net::SMTP"

BODY = "Hi Bob,\nThis message was sent with pure Ruby 3.3.\n"

 

message = <<~MSG

  From: #{FROM}

  To: #{TO}

  Subject: #{SUBJ}

 

  #{BODY}

MSG

 

Net::SMTP.start(

  "smtp.your-provider.com", # host

  587,                      # port

  "localhost"               # HELO domain

) do |smtp|

  # comment out the next line if the server requires no auth

  smtp.login("smtp_user", "smtp_password")

  smtp.enable_starttls_auto # explicit TLS

  smtp.send_message(message, FROM, TO)

end

 

Key points:

  • One file, one ruby email send. Copy the code snippet into a file and run ruby send.rb.

  • OpenSSL is ready. Ruby 3.3 is compiled with OpenSSL 3.x, so the TLS handshake works out of the box.

  • No dependencies. The script uses only net/smtp and openssl, both included in the core distribution.

TLS, STARTTLS, and Authentication

Setting

What it does

How to set it in Net::SMTP

TLS (SMTPS, port 465)

Opens the socket inside an encrypted tunnel right from the start.

smtp.enable_tls before smtp.start.

STARTTLS (port 587)

Starts unencrypted, then upgrades after the STARTTLS command.

smtp.enable_starttls_auto inside the block, as shown above.

User name / Password

Credentials for the server’s “AUTH LOGIN” or “AUTH PLAIN”.

Call smtp.login("user", "pass") right after enabling TLS.

HELO / EHLO domain

Identifies your host in the SMTP greeting. Must be a valid FQDN or IP.

Third parameter of Net::SMTP.start, e.g. "localhost".

Common pitfalls

  1. Self-signed certificates.
    Your script will fail with OpenSSL::SSL::SSLError if the server presents a self-signed or expired certificate. In tests, set smtp.enable_starttls_auto(OpenSSL::SSL::VERIFY_NONE) only on a trusted internal network. Never disable verification in production.

  2. Wrong port and mismatched TLS mode.
    Port 465 expects immediate TLS. Port 587 expects STARTTLS. Mixing them gives “connection reset” or “handshake failure” errors.

  3. Blocked outbound port.
    Some hosting providers close 25, 465, or 587 ports. If the script times out, first check firewall rules.

  4. Invalid HELO string.
    Many SMTP relays reject generic identifiers like “localhost”. Use the public hostname of the sending machine.

With these knobs set correctly, Net::SMTP remains the fastest route for a quick, single-node mail sender – and the foundation for higher-level Ruby mail libraries you will meet later in the guide.

For sending emails in Ruby, we must speak the same language as the server. Four settings – hostname, port, credentials, and encryption mode – decide whether your email flies out in seconds or comes back as a cryptic error. Mix up just one of them, and Net::SMTP may hang forever or the provider will silently block your message. Let's talk about each field in plain words and then plug real values for Gmail, AWS SES, and UniOne, so you can copy-paste with confidence instead of guessing on production.

Hostname, Port, Credentials, Secure Connection  –  What They Mean

  • Hostname – the fully qualified domain name (FQDN) of the provider’s SMTP gateway (for example, smtp.gmail.com).

  • Port – the TCP endpoint that accepts mail traffic. Port 465 expects an immediate TLS tunnel (SMTPS). Port 587 starts plain and then upgrades with the STARTTLS command.

  • Credentials – the login and password pair that proves you own the account. Most services still accept a classic username and password string, but some (like Gmail) now require an app-specific password or OAuth2 token.

  • Secure connection – the encryption mode. Use TLS wrapper when you connect to 465, or STARTTLS on 587. Never send real mail over an unencrypted channel.

Keep these four values at hand; every code sample below plugs them into the same Net::SMTP call.

Gmail, AWS SES, UniOne SMTP Service

Below are three cut-and-paste settings blocks for the code discussed above. Replace the placeholders with your real values. Each example calls smtp.enable_starttls_auto, because port 587 is the best default option in 2025.

Gmail (Google Workspace or personal Gmail)

require "net/smtp"

 

Net::SMTP.start(

  "smtp.gmail.com",     # hostname

  587,                  # port (STARTTLS)

  "localhost",          # HELO

  "[email protected]",     # user name

  "APP_PASSWORD",       # 16-char app password

  :login                # auth type

) do |smtp|

  smtp.enable_starttls_auto

  smtp.send_message msg, from, to

end

Gmail also supports port 465 with wrapper TLS if your firewall blocks 587.

Amazon SES (US-East-1 region example)

require "net/smtp"

 

Net::SMTP.start(

  "email-smtp.us-east-1.amazonaws.com",

  587,

  "localhost",

  "AKIAXXXXXXXX",   # SMTP user (generated in SES)

  "SES_SECRET",     # SMTP password

  :login

) do |smtp|

  smtp.enable_starttls_auto

  smtp.send_message msg, from, to

end

Region prefixes vary: pick the one that matches your SES console (eu-central-1, ap-southeast-2, etc.)

UniOne

Send emails in Ruby with UniOne SMTP

require "net/smtp"

 

Net::SMTP.start(

  "smtp.unione.io",   # hostname shown in your UniOne dashboard

  587,

  "localhost",

  "UNIONE_USER",      # login from “SMTP Settings”

  "UNIONE_KEY",       # password (API key works too)

  :plain              # UniOne accepts PLAIN over TLS

) do |smtp|

  smtp.enable_starttls_auto

  smtp.send_message msg, from, to

end

After the first successful send you can open UniOne’s dashboard to view delivery stats and troubleshoot any issues with the built-in SMTP Service Debug Tool

Tip: if port 587 is blocked by your hosting provider, switch to port 465 and replace enable_starttls_auto with enable_tls. Always keep SSL verification on unless you are inside a closed test-only network.

ActionMailer: Rails-Native Email Delivery

ActionMailer is part of every Rails project. It works like a small MVC layer that is focused solely on email. You create one Ruby class for each mail purpose, one view for the text part, and one view for the HTML part, and then call a single method to send or queue the message. Because it is integrated into Rails, you get environment-specific settings, background jobs, and test helpers for free.

From Configuration to Delivery

Below is a complete, production-ready path. Follow the steps given for a Rails 7.1 application. Replace placeholders with your real data.

Step 1 – Tell Rails where and how to send

Open config/environments/development.rb and config/environments/production.rb.
Add or adjust the block:

config.action_mailer.smtp_settings = {

  address:              "smtp.unione.io", # hostname from your provider

  port:                 587,              # 465 if you prefer wrapper TLS

  user_name:            ENV.fetch("SMTP_USER"),

  password:             ENV.fetch("SMTP_PASS"),

  authentication:       :plain,           # :login also works for most hosts

  enable_starttls_auto: true,             # upgrades to TLS on port 587

  open_timeout:         5,                # seconds to wait for TCP connect

  read_timeout:         5                 # seconds to wait for server reply

}

 

config.action_mailer.default_options = {

  from: "Support <[email protected]>"

}

 

config.action_mailer.default_url_options = {

  host:     "your-app.example.com",

  protocol: "https"

}

Notes:

  • address and port point to the SMTP relay.

  • user_name and password verify you are allowed to send. Keep them in ENV so that they never land in Git.

  • authentication chooses the SASL method. :plain and :login are widely accepted once TLS is active.

  • enable_starttls_auto triggers the STARTTLS command, so the rest of the session is encrypted.

  • Time-outs stop your web request from hanging if the mail server stalls.

  • default_options[:from] sets a global sender. You can override it per email if needed.

  • default_url_options lets URL helpers (link_to, url_for) build absolute links inside templates.

Step 2 – Generate a mailer class

Run the generator:

bin/rails generate mailer UserMailer

 

Rails creates these files and folders:

  • app/mailers/user_mailer.rb – the Ruby class;

  • app/views/user_mailer/ – a folder for templates;

  • test/mailers/user_mailer_test.rb – a basic Minitest file.

Edit the class:

class UserMailer < ApplicationMailer

  # ApplicationMailer already inherits the SMTP settings and default options.

 

  def welcome(user)

    @user = user    # ← personalisation variable

    mail(

      to:      @user.email,

      subject: "Welcome, #{@user.first_name}!"

    )

  end

end

What happens here:

  • welcome is a public method, so Rails exposes it as one mail “action.”

  • @user becomes available inside both the text and HTML templates.

  • The mail call builds a multipart message automatically; no MIME code is needed.

Step 3 – Write the templates

Create app/views/user_mailer/welcome.text.erb:

Hi <%= @user.first_name %>,

 

Thank you for joining our platform. We are happy to meet you!

 

 -  The Team

Create app/views/user_mailer/welcome.html.erb:

<!DOCTYPE html>

<html>

  <body>

    <p>Hi <%= @user.first_name %>,</p>

    <p>Thank you for joining our platform. We are happy to meet you!</p>

    <p> -  The Team</p>

  </body>

</html>

Rails recognises the same base name (welcome) and format extension (.text or .html) and wraps them in a multipart/alternative container. Mail clients that lack HTML support fall back to the plain-text part.

Step 4 – Add an attachment (optional)

Inside the welcome action, right before the mail call:

attachments["Terms.pdf"] = File.read(Rails.root.join("docs/Terms.pdf"))

 

Attachment details:

  • The key "Terms.pdf" sets the filename as the recipient will see it.

  • Rails guesses the MIME type from the extension.

  • For inline images, set attachments.inline:
    attachments.inline["logo.png"] = File.read("app/assets/images/logo.png")
    Then reference it in HTML with <img src="cid:logo.png">.

Step 5 – Preview the email in the browser

Development previews keep you out of real inboxes while you design.

1. In config/environments/development.rb ensure:
config.action_mailer.show_previews = true

2. Create test/mailers/previews/user_mailer_preview.rb:

class UserMailerPreview < ActionMailer::Preview

  def welcome

    user = User.new(first_name: "Alice", email: "[email protected]")

    UserMailer.welcome(user)

  end

end

3. Visit http://localhost:3000/rails/mailers.
    Click UserMailer#welcome to view both parts plus raw MIME.

Step 6 – Send now or later

Fire instantly:

UserMailer.welcome(@user).deliver_now

 

Or queue for later:

UserMailer.welcome(@user).deliver_later(wait_until: 2.hours.from_now)

 

Background engine:

  • In development, deliver_later uses the inline queue adapter, so the mail still sends during the request.

  • In production, set a real queue adapter (Sidekiq, Resque, Delayed Job) in config/application.rb:

config.active_job.queue_adapter = :sidekiq

 

Sidekiq workers pull the job, connect to the SMTP relay, and retry on transient failures without blocking the web server.

Step 7 – Test deliveries automatically

In Minitest:

test "welcome email is queued" do

  user = users(:alice)

  assert_enqueued_emails 1 do

    UserMailer.welcome(user).deliver_later

  end

end

In RSpec:

expect {

  UserMailer.welcome(user).deliver_now

}.to change { ActionMailer::Base.deliveries.size }.by(1)

Rails clears ActionMailer::Base.deliveries between tests, so your suite stays independent of external services.

Step 8 – Handle errors and edge cases

  • SMTP time-outs trigger Net::OpenTimeout or Net::ReadTimeout. Wrap deliver_now calls in rescue blocks when you cannot afford a hard failure.

  • Invalid addresses bounce inevitably. Use quick format validation (URI::MailTo::EMAIL_REGEXP) before sending to eliminate malformed addresses.

  • Attachments increase the MIME size by ~33% due to Base-64. Many providers cap the full message at 25MB or less.

ActionMailer keeps all mail logic inside your Rails project:

  1. A single config section defines the relay and secrets.

  2. One method equals one email type; the code looks like a normal controller action.

  3. Templates behave like regular Rails views, so every front-end skill you already have (ERB, Slim, Haml, i18n) applies.

  4. Attachments, inline images, and personalisation reuse standard Ruby objects and hash assignments – no special DSL.

  5. deliver_later plugs into ActiveJob, giving you background retries and delayed scheduling with almost no extra code.

  6. Built-in previews and test helpers let you design, debug, and assert delivery without touching a live inbox.

With these tools in place you can ship production-grade emails, keep them under version control, and evolve them side by side with the rest of your Rails application.

Stand-Alone Ruby Email Gems

Not every project sending emails in Ruby runs on Rails. If you ship a CLI tool, a background worker, or a plain Rack app, you may need a mail library that works without the full Rails stack. The ecosystem offers several such gems; the most mature and flexible one is simply called Mail. It wraps the same SMTP settings you used earlier and hides the low-level protocol under a clean Ruby DSL. Thanks to ruby mail conventions, even a short script can handle headers and MIME parts correctly. In the next two subsections, you’ll learn how to install the gem, send a basic message, and then enrich that message with attachments and inline images.

Sending with the Mail Gem

  1. Install the gem

    Add it to your project:

    gem install mail

    # or, in a Gemfile

    gem "mail", "~> 2.8"

 

  1. Configure the delivery method

    Create a setup file, for example mailer_config.rb:

    require "mail"

     

    Mail.defaults do

      delivery_method :smtp,

        address:              "smtp.unione.io",

        port:                 587,

        user_name:            ENV["SMTP_USER"],

        password:             ENV["SMTP_PASS"],

        authentication:       :plain,

        enable_starttls_auto: true

    end

Note: Each key mirrors the meaning explained in the SMTP settings section. No additional libraries are needed; net/smtp and OpenSSL are already included in Ruby 3.3. That means every example here works with the default ruby mail tool chain.

  1. Build and send a plain text email

    require_relative "mailer_config"

     

    mail = Mail.new do

      from     "alice@example.com"

      to       "bob@example.com"

      subject  "Hello from the Mail gem"

      body     "Hi Bob,\nThis email was sent with a few lines of Ruby.\n"

    end

     

    mail.deliver!   # the bang method raises if delivery fails

Note:  Mail.new collects headers and the body;  deliver! opens one SMTP session, sends the message, and returns the same Mail object.

Attachments and Inline Images

The same DSL lets you mix text, HTML, and files without manual MIME work.

require_relative "mailer_config"

 

mail = Mail.new

mail.from     = "alice@example.com"

mail.to       = "bob@example.com"

mail.subject  = "Company newsletter, July 2025"

 

# Plain-text part

mail.text_part = Mail::Part.new do

  body "Hi Bob,\nPlease see the attached PDF and the inline chart below.\n"

end

 

# HTML part

mail.html_part = Mail::Part.new do

  content_type "text/html; charset=UTF-8"

  body <<~HTML

    <p>Hi Bob,</p>

    <p>Please see the attached PDF and the inline chart below.</p>

    <p><img src="cid:chart.png" alt="Monthly chart"></p>

  HTML

end

 

# Regular attachment

mail.attachments["Report.pdf"] = {

  mime_type: "application/pdf",

  content:   File.read("report.pdf")

}

 

# Inline image (Content-ID)

mail.attachments.inline["chart.png"] = File.read("chart.png")

 

mail.deliver!

How it works:

  • text_part and html_part create a multipart/alternative container automatically.

  • attachments["Report.pdf"] adds a separate MIME part; most clients show it under a paperclip icon.

  • attachments.inline["chart.png"] sets a Content-ID header. The HTML code references that image with cid:chart.png, so the client shows it inside the message instead of as a download.

  • deliver! handles the whole package in one SMTP transaction.

Other lightweight gems

If you want the shortest possible code, look at Pony and Letter Opener.

  • Pony wraps the Mail gem in one helper. A full send looks like:

require "pony"

 

Pony.mail(

  to:   "[email protected]",

  from: "[email protected]",

  subject: "Quick note",

  body: "Sent with one line!",

  via: :smtp,

  via_options: {

    address: "smtp.unione.io",

    port: 587,

    user_name: ENV["SMTP_USER"],

    password: ENV["SMTP_PASS"],

    enable_starttls_auto: true

  }

)

Everything you learned about host, port, TLS, and credentials still applies; Pony only hides the boilerplate.

  • Letter Opener swaps the real delivery method for a local preview. When you call mail.deliver!, the gem opens a browser tab and shows the exact HTML. No SMTP, no external API — perfect for development and design review.

Send emails in Ruby with Pony - UniOne Blog

Both gems rely on the same Mail engine under the hood, so headers, attachments, and inline images behave exactly like in the longer examples above. In daily work, this shared core makes debugging any ruby mail issue much easier. Use Pony when you need one-shot sends with minimal code, and pair it with Letter Opener when you want to preview the email without actually sending it.

Building, Testing, and Scheduling Emails

Sending one message during development is easy; running large, personalized campaigns on a live server is harder.
You must merge unique data for each recipient, keep delivery reliable even when sending thousands of messages, queue work so it does not slow the server process, and test every template before real users see it. Let's explain these matters step by step and demonstrate code that works in both plain Ruby and Rails.

Personalization, Bulk Sends, Attachments

When sending emails with ruby, personalization means inserting a user’s own data – name, order total, reset token – into the email. The Mail gem uses simple Ruby interpolation, exactly like ActionMailer.

require_relative "mailer_config"    # loads SMTP settings

require "csv"

 

template = <<~TEXT

  Hi %{first_name},

 

  Your current balance is %{balance} USD.

TEXT

 

# read a CSV exported from your database

CSV.foreach("customers.csv", headers: true) do |row|

  body = format(template, first_name: row["first_name"],

                          balance:     row["balance"])

 

  Mail.deliver do

    from     "[email protected]"

    to       row["email"]

    subject  "Balance update"

    body     body

    add_file "terms.pdf"                 # static attachment reused for everyone

  end

end

How the loop works:

  1. The code loads one line from customers.csv, builds the body with format, and calls Mail.deliver.

  2. Each iteration opens a fresh SMTP session by default. For huge lists, reuse one session by wrapping the loop in Mail::SMTPConnection.new.start { … }.

  3. Attachments are added with add_file. They travel once per each email, so large files can slow the run; consider hosting big PDFs and include a link instead.

If you run Rails, replace Mail.deliver with UserMailer.balance(user).deliver_later, where user is an Active Record object. The personal fields then come from instance variables inside the mailer template.

Scheduling with ActiveJob or Sidekiq

When sending emails in Ruby, why do you need scheduling? A password reset must go out at once, but a monthly report can wait for off-peak hours. Rails gives you deliver_later, which pushes the message into the ActiveJob queue. A queue adapter such as Sidekiq pulls jobs and handles retries.

# app/jobs/monthly_report_job.rb

class MonthlyReportJob < ApplicationJob

  queue_as :mailers                       # keep mail traffic off the default queue

 

  def perform(user_id)

    user = User.find(user_id)

    ReportMailer.monthly(user).deliver_now

  end

end



# schedule the job on the 1st day of next month, 03:00 server time

run_at = Time.current.beginning_of_month.next_month + 3.hours

MonthlyReportJob.set(wait_until: run_at).perform_later(user.id)

Sidekiq setup:

# config/application.rb

config.active_job.queue_adapter = :sidekiq

Sidekiq workers reconnect to SMTP if the first try fails, with exponential back-off. You see each retry in the Sidekiq Web UI, so you can fix credentials quickly.

Pure Ruby apps (without Rails) often use a cron-style gem:

require "rufus-scheduler"

 

scheduler = Rufus::Scheduler.new

 

scheduler.cron "0 3 1 * *" do            # 03:00 on the first day every month

  send_monthly_reports                    # your own method that loops Mail.deliver

end

 

scheduler.join

rufus-scheduler runs inside one long-living process; combine it with a systemd service or Docker container to keep it alive in production.

Template Management and UniOne Email Testing

Template engines inside Ruby:

  • ERB is built in. It mixes plain text or HTML with <%= %> tags.

  • Haml, Slim, and Liquid are popular gems for those who prefer shorter syntax or need strict separation between logic and markup.

  • Every engine renders to a complete string. The mailer layer then wraps that string in the MIME container.

<!-- app/views/user_mailer/reset_password.html.erb -->

<h1>Hello <%= @user.first_name %>,</h1>

<p>Click the link below to reset your password:</p>

<p><a href="<%= @reset_url %>">Reset Password</a></p>

Note: When using email APIs, you can store reusable templates on the provider’s side instead. UniOne lets you upload HTML once, assign a template ID, and pass only dynamic fields in the API call – handy when several micro-services share one email style.

Before you hit Send on thousands of addresses, use UniOne’s email testing tool to check if your code works properly. This tool captures the full SMTP session log, allowing you to catch any problems before actual mailing.

 

Local preview options you may use instead of real rendering in mail clients include:

  • ActionMailer previews show the final HTML in your browser but do not test remote CSS or embedded images.

  • Letter-Opener (development gem) opens each email in a new browser tab instead of sending it.

  • mailcatcher runs a tiny “fake” SMTP server on localhost:1025; point your dev config to it and view every message in a web UI.

Version control and rollback advice:

Keep every template file under Git like normal source code. When a designer updates branding colors, you can review the diff, test the new view, and revert quickly if a bug slips through.

UniOne’s Email API Integration with Ruby

SMTP works, but you still own every reconnect, retry, and metric; when you look for email API ruby solutions, UniOne’s REST interface removes that burden. UniOne’s REST API moves that weight to the provider: you post one single payload for multiple sends, and UniOne queues, signs, retries, and records each event. The official unione-ruby gem wraps every call in plain Ruby, so you can stay inside the language you already use.

Setup and Basic Send Using the UniOne-ruby Gem

Step 1 – Install the gem

gem install unione-ruby

# or inside Gemfile

gem "unione-ruby", "~> 1.6"

Step 2 – Store your API key safely

export UNIONE_API_KEY="uNioNe.xxxxx.yyyyy"

Never hard-code the key — use ENV or a secrets manager.

Step 3 – Create a client

require "unione"

 

client = UniOne::Client.new(

  api_key: ENV["UNIONE_API_KEY"],

  # optional extras:

  hostname: "https://api.unione.io/en/", # point to a regional host if needed

  timeout: 10,                           # seconds; default is 5

  enable_logging: true,                  # prints each request to STDOUT

  api_key_in_params: false               # set true if an old proxy strips headers

)

Step 4 – Build and send one email

require "base64"

 

message = {

  recipients: [

    { email: "[email protected]",

      substitution_data: { first_name: "Bob" } }

  ],

  subject: "Contract for #{Time.now.year}",

  body: {

    html: "<p>Hi *|first_name|*,<br>Please see the signed contract attached.</p>",

    text: "Hi *|first_name|*,\nPlease see the signed contract attached."

  },

  attachments: [

    {

      type: "application/pdf",

      name: "contract.pdf",

      content: Base64.strict_encode64(File.binread("contract.pdf"))

    }

  ]

}

 

response = client.send_email(message)

 

# Response can be read two ways:

puts response.body["status"]   # hash style

puts response.body.status      # dot style (OpenStruct helper)

puts "Message ID: #{response.body.message_id}"

What happens here:

  • recipients takes one or many addresses; UniOne handles the fan-out.

  • substitution_data merges values into placeholders like *|first_name|*.

  • attachments.content uses Base-64 — one line with Base64.strict_encode64.

  • The call returns a parsed JSON object; you can access keys as hash or dot.

For the full list of endpoints and fields, see UniOne’s Email API Service for Developers documentation page.

Advanced Features: Webhooks, Statistics, Projects

UniOne’s API covers all the tasks you meet after the first send.

Webhooks – tell UniOne where to POST mail-related events; you get JSON in real time for each email being delivered, hard_bounced, spam, opened, and more.

client.set_webhook(

  url: "https://app.example.com/unione_events",

  events: %w[delivered hard_bounced spam],

  is_active: true

)

To trust only real callbacks, add:

client.verify_callback_auth!(request.params) # HMAC check

 

…and to parse many events:

client.callback_helper(request.params) do |event|

  case event["event"]

  when "delivered"     then mark_delivered(event["email"])

  when "hard_bounced"  then disable_user(event["email"])

  end

end

Statistics – pull aggregated numbers for dashboards or A/B tests.

Track email delivery statistics in Ruby with UniOne

stats = client.get_statistics(

  start: Date.today - 7, end: Date.today, group: "day"

)

stats.body.stats.each do |row|

  puts "#{row.date}: sent #{row.sent}, opens #{row.opened}"

end

Projects – keep staging mail, production mail, and white-label clients separate under one master account.

Tags – add tag: "receipt" in any send to slice stats later.

Skip-unsubscribe – set skip_unsubscribe: 1 for critical system mail (like password resets) so it bypasses marketing opt-outs.

Template store – upload HTML once, receive a template_id, and send only dynamic fields – lighter payloads and a single design source.

Error handling – wrap calls to catch service-side issues:

begin

  client.send_email(message)

rescue UniOne::Error => e

  logger.error "UniOne API error: #{e.message}"

end

Tips:

  • One gem covers sending, webhooks, stats, projects, and template storage.

  • HTTPS with an API key is simpler and safer than raw SMTP credentials.

  • Helpers (verify_callback_auth!, callback_helper) cut dozens of lines from webhook controllers.

  • Optional flags (timeout, enable_logging, api_key_in_params) adapt the client to slow links and legacy proxies.

  • All features stay in clean Ruby objects — no manual JSON parsing.

UniOne Git source and documentation: https://github.com/unione-repo/unione-ruby

Conclusions

People who google “ruby send email” want to know which delivery layer matches their scope and future load. Every tool we met in this guide can push an email; what changes is the cost of maintenance, the depth of feedback, and how much code you must write yourself.

Choose the right Ruby mail tool - UniOne Blog

  • Net::SMTP is the lightest option. Because it lives inside Ruby, you need no extra gems and no framework. It suits quick one-off scripts, cron jobs on a private network, or any place where you need to craft every header by hand. The price is that you also own the hard parts – retry loops, bounce parsing, TLS updates, and scaling across servers. If the script grows, the manual work piles up fast.
  • Mail gem keeps you on bare Ruby but hides MIME details behind a DSL. It is ideal for command-line tools, background daemons, or Sinatra apps that do not need the weight of Rails. You still talk SMTP under the hood, so the same delivery limits apply, but code stays readable and attachments are one line instead of a page of boundary markers.
  • ActionMailer is the default choice for any Rails project. It plugs into the framework’s view engine, ActiveJob, and test helpers, so you gain previews, background queues, and clean templates without extra libraries. For moderate traffic – say tens of thousands of messages per day – ActionMailer is usually enough. Past that point you hit two pain points: keeping multiple SMTP sessions open and collecting reliable metrics. Both push teams toward an API.
  • UniOne’s REST API removes the session layer completely. You post JSON, UniOne queues the job, retries, signs with DKIM, and reports every event back to your webhook. That means you gain delivery stats, open/click tracking, and separate projects for staging versus production, all without writing additional infrastructure. The gem gives you one Ruby class that covers sends, webhooks, stats queries, and template storage. For high-volume or multi-service systems, the time you save on scaling and monitoring is greater than the learning curve of moving from SMTP to REST.

Picking the tool to send emails in Ruby - UniOne Blog

Rule of thumb:

  • Tiny script, single server → start with Net::SMTP or the Mail gem.

  • Rails app, normal traffic → use ActionMailer over SMTP; queue with Sidekiq when you need bursts.

  • Any app where deliverability tracking, fast retries, or multi-tenant reporting matters → switch to the UniOne Email API and let the platform own the operational burden.

By mapping each project to its real needs – control, simplicity, or scale - you can pick the smallest tool that still leaves room for growth and continue to send emails in ruby with zero friction.

For teams sending emails with ruby, UniOne is more than a mail sender. It gives you a set of compact tools that solve the various jobs around delivery, testing, and reporting. Each tool works on its own yet fits into the same web dashboard, so you keep one login and one source of truth.

SMTP Service – lets any script or framework use UniOne as a drop-in mail relay. Point your host, port, and credentials to the values in your dashboard, then keep the rest of your code unchanged. The service handles TLS, rate limits, and retry queues, so you focus on content, not connection logic.

Email API – When you need deeper control, switch to the Email API for developers. One HTTPS call sends the message and returns a JSON ID. You can tag messages, pass merge fields, attach files, and pull real-time status with the same key. This method scales better than raw SMTP because UniOne batches retries and stores statistics automatically.

Email Testing – Setup errors or broken code leads to campaign failures. The SMTP debug tool helps pinpoint any issues you may encounter prior to sending real emails. Get the full SMTP session log and fix the errors before your customers see them.

Webhooks – UniOne can push mail events – delivered, bounced, opened, clicked – straight to your endpoint. You receive clean JSON, so a few lines of code update user records or trigger workflows the second an event happens. Webhooks remove the need for endless polling loops.

Statistics dashboards – Inside the dashboard you get charts for sending volume, bounces, opens, clicks, and complaint rate, grouped by day, project, or custom tag. Export the data as CSV or query it through the API to feed your own BI tools. This view helps you catch deliverability drops early and prove campaign results to stakeholders.

 

FAQ

Q 1. What is the maximum attachment size I can send?
A single email should stay under 10 MB of attachments (base64 encoded). UniOne’s deliverability team recommends this limit because larger messages are often blocked or clipped by receiving servers and mobile clients. If the file is bigger, upload it to cloud storage and place a download link in the body.

Q 2. Can I send one message to 10 000 recipients?
Yes. The safest way is to call the Email API once with a recipients array or loop over the list and call send_email in batches of a few hundred. From a throughput angle, the API can handle millions of emails per hour, while SMTP can push up to 5 000 emails per hour per connection (50 000 with ten parallel connections), so 10 000 recipients is well within normal limits.

Q 3. How do I track and handle bounces and complaints?
Enable webhooks in your UniOne dashboard (or with the webhook/set API call). UniOne POSTs JSON events such as soft_bounced, hard_bounced, or spam to your endpoint. The system retries delivery for soft bounces for up to 48 hours and blocks hard-bounced addresses automatically. You only need to parse the event and update your database.

Q 4. Which Ruby versions does the unione-ruby gem support?
The gem is tested on Ruby 2.5 through Ruby 3.3 (the current LTS and latest stable releases). Since the client is a thin REST wrapper with no native extensions, sending emails in Ruby usually works on older 2.4 systems too, but upgrading to an actively maintained Ruby version is strongly advised for TLS security and performance.

 

Related Articles
Blog
For beginners
Top 5 Call Tracking Cloud PBX Software To Use In 2024
In the modern data-driven world having a reliable Private Branch eXchange (PBX) solution isn’t enoug
Vitalii Piddubnyi
03 july 2024, 12:188 min
Blog
For experts
Email Obfuscation: Which Methods are the Best?
Public email addresses have long been popular targets for spammers. Many spammers deploy web crawler
Alexey Kachalov
20 september 2023, 09:204 min
Blog
For beginners
What Is an Email Marketing Strategy: a Complete Guide
Email is one of the oldest and most widely-used forms of digital communication. With over 4 billion
Valeriia Klymenko
15 november 2022, 09:3013 min