Send email with Ruby

Skip the ActionMailer SMTP config. Sending email from Ruby is one POST from Net::HTTP or Faraday, message ID back.

Verified By George Hartley, Co-founder · Updated July 20, 2026

ActionMailer config vs one POST

ActionMailer earns its keep for templated app mail inside Rails, but it still rides on SMTP settings: address, port, user_name, password, authentication, enable_starttls_auto. Six config keys before the first send, and rotating credentials touches every environment.

One POST, and the send is handled

RubyPOST /v1/my/messages
require "net/http"
require "json"

Net::HTTP.post(
  URI("https://api.nitrosend.com/v1/my/messages"),
  { from: "[email protected]", to: "[email protected]",
    subject: "Welcome", html: "<p>You're in.</p>" }.to_json,
  "Authorization" => "Bearer #{ENV['NITROSEND_API_KEY']}",
  "Content-Type" => "application/json",
)

Works from Rails, Sinatra, a Rake task, or a bare script. One env var, standard library only.

For scale of what sits behind that POST: I recently watched four flows and fourteen emails get designed, written, and set sending in about twenty-three minutes. Builders who grew up on Rails scaffolding will recognise the feeling.

And here's the part Ruby people will enjoy: the API on the other end of that POST is a Rails 8.1 app, PostgreSQL and GoodJob underneath. Your Net::HTTP call is speaking to a stack you already know how to reason about.

A design detail from inside that codebase: every production send path, campaign, flow, transactional, and the MCP tool included, runs through one central sending gate, a single check on every dispatch. A paused account fails loudly with metadata saying exactly why, instead of six code paths each deciding for themselves. And when SES suppresses a recipient mid-flow, the failure is written to the send log and that branch stops, without failing the whole GoodJob run. Rails people know why both of those matter.

Full parameters, responses, and error codes live in the REST API docs and the API reference.

Python Node.js PHP Go Rust +5
send.py requests ยท stdlib-adjacent
import requests

requests.post(
  "https://api.nitrosend.com/v1/messages",
  headers={"Authorization": f"Bearer {KEY}"},
  json={"to": to, "subject": subj,
        "html": body})
202 ACCEPTED
{ "id": "msg_9f2c",
  "status": "accepted" }

And the part no library gives you

The same account exposes an MCP server, so the agent writing your code can also run your email. Point Claude, Cursor, or Codex at api.nitrosend.com/mcp and it composes, previews, and stages sends, with a human approving every one. The API is for your code. The MCP is for your agent. One platform either way.

Every send comes back with a message ID, and delivery events follow on your webhook: delivered, opened, bounced. No dashboard required to know what happened.

Go deeper

Guides

First send in thirty seconds.

Simple pricing. Unlimited contacts.

Every plan includes full stack emailing: Flows, Newsletter Campaigns and Transactional Email, plus our NitroWheel LLM and all agent integrations (Claude, ChatGPT, Codex, Cursor and others). Pay for what you send, not who you store.

Free
$0
forever
  • Emails 8,000then 500/mo
  • Email types Transactional & Marketing
  • AI actions 20/mo
  • Contacts Free & Unlimited
  • Brands 1
  • Seats 1
Start free
Ultra
$100
per month
  • Emails 125,000/month
  • AI actions 5,000/mo
  • Brands 5
  • Seats 10 · Domains 3
  • Frontier AI Included
  • Dedicated IP Available
Get started
Enterprise
$300
per month
  • AI actions Unlimited
  • Unlimited brands & domains Included
  • SSO / SAML Included
  • 99.9% SLA Included
Get started

Free forever. No credit card required. See full comparison →