Send email with Ruby through Volanea by calling the REST API from your server-side application. This guide uses Ruby’s built-in HTTP libraries plus a small environment-variable helper, so you can make a straightforward authenticated request without depending on a provider-specific gem.

The example below is intentionally built around standard REST conventions: an HTTPS POST request, JSON request body, API-key authentication, and explicit error handling. Before running it, copy the exact sending endpoint and any authentication requirements for your Volanea account from the email API reference and setup guides. That keeps your integration aligned with the current API contract instead of hard-coding assumptions about a Ruby SDK or endpoint version.

What you need before you send email with Ruby

A transactional email integration has a few prerequisites that code alone cannot replace. Set these up first so that the first successful API response corresponds to a message that can actually be accepted and delivered.

You need:

  • A Volanea account with an API key intended for server-side use.
  • A sending domain or sender identity configured according to the requirements shown in your account and API documentation.
  • A verified sender address on that domain, such as notifications@example.com.
  • Ruby installed locally or available in your deployment environment.
  • A recipient address that you are allowed to send a test message to.
  • The exact Volanea email-send endpoint documented for your account.

Do not place a sending API key in browser JavaScript, a mobile application, a public repository, or a client-side desktop app. Anyone who obtains a sending credential may be able to use your email infrastructure. Ruby code should run in a trusted server, worker, command-line tool used by an authorized operator, or an isolated background job environment.

Why this guide uses REST instead of a Ruby-specific SDK

Ruby does not require a provider SDK to make an email API request. Net::HTTP, URI, and JSON are part of Ruby’s standard library, which means the core request path has fewer runtime dependencies and is easy to inspect during troubleshooting.

A direct REST approach is especially useful when you need to control headers, timeouts, retries, logging, or endpoint configuration yourself. It also avoids presenting an invented method such as Volanea::Emails.send as though it were an official, supported Ruby API. The request still needs to follow the current Volanea documentation for its URL, authentication scheme, and accepted JSON fields.

Install the Ruby dependency

The sending request itself uses Ruby standard-library components. This guide installs dotenv so that a local .env file can hold development values without adding secrets directly to send_email.rb.

Run this command from your project directory:

gem install dotenv

If your Ruby project uses Bundler, add the dependency to its Gemfile instead:

gem "dotenv"

Then install project dependencies:

bundle install

For a simple standalone script, gem install dotenv is enough. For a Rails, Sinatra, Hanami, or other Bundler-managed application, prefer the Gemfile approach so the dependency version is captured with the rest of the application.

Check your Ruby version

Confirm that Ruby is available:

ruby --version

This example uses syntax supported by modern Ruby releases, including keyword arguments and ENV.fetch. If your production host is running an old Ruby version, upgrade it before deploying new infrastructure code. Email delivery code often runs in critical paths such as account verification, password recovery, payment receipts, and security alerts; avoiding an unsupported runtime is part of making those paths dependable.

Configure environment variables

Create a file named .env in the same directory as your Ruby script. This file is for local development only. Do not commit it to Git.

VOLANEA_API_KEY=replace-with-your-server-side-api-key
VOLANEA_SEND_ENDPOINT=https://replace-with-the-exact-volanea-send-endpoint-from-the-docs
VOLANEA_AUTH_SCHEME=Bearer
VOLANEA_FROM=Example App <notifications@example.com>
VOLANEA_TO=you@example.net

There are two deliberately configurable values in this setup:

  • VOLANEA_SEND_ENDPOINT is the exact HTTPS email-send URL from the current Volanea API documentation.
  • VOLANEA_AUTH_SCHEME defaults to Bearer in the sample, but you should change it if the API reference for your key specifies a different authentication format or header requirement.

Keeping the endpoint outside your source code makes it simpler to use separate test and production environments. It also means your deployment configuration can be updated without editing an application file if an endpoint version changes.

Add .env to .gitignore

Create or update .gitignore:

.env

Do not treat .gitignore as the only protection for a secret. If a key has already been committed, pasted into an issue, printed in CI logs, or shared in a screenshot, rotate it. A deleted commit or hidden log line may still be accessible through repository history, caches, artifacts, or backups.

Production environment variables

In production, configure the same values through your hosting provider, deployment system, container platform, secrets manager, or CI/CD environment. The application should receive VOLANEA_API_KEY as an environment variable at runtime.

Avoid copying a development .env file onto a server. Production secrets should be separately managed, access-controlled, and rotated when required. Use different credentials for development, staging, and production when your account setup permits it, so a local test cannot accidentally use a production sending identity.

Complete Ruby example

Save the following file as send_email.rb. It loads configuration from environment variables, initializes the API key from VOLANEA_API_KEY, builds one transactional-email JSON payload, performs an HTTPS POST, applies explicit timeouts, and prints either the success response or a useful failure message.

require "dotenv/load"
require "json"
require "net/http"
require "uri"

api_key = ENV.fetch("VOLANEA_API_KEY")
endpoint = ENV.fetch("VOLANEA_SEND_ENDPOINT")
auth_scheme = ENV.fetch("VOLANEA_AUTH_SCHEME", "Bearer")
from = ENV.fetch("VOLANEA_FROM")
to = ENV.fetch("VOLANEA_TO")

uri = URI.parse(endpoint)

unless uri.is_a?(URI::HTTPS)
  raise "VOLANEA_SEND_ENDPOINT must use HTTPS"
end

payload = {
  from: from,
  to: [to],
  subject: "Welcome to Example App",
  text: "Thanks for creating an account. Your email integration is working.",
  html: <<~HTML
    <h1>Welcome to Example App</h1>
    <p>Thanks for creating an account.</p>
    <p>Your email integration is working.</p>
  HTML
}

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.open_timeout = 5
http.read_timeout = 15

request = Net::HTTP::Post.new(uri.request_uri)
request["Authorization"] = "#{auth_scheme} #{api_key}"
request["Content-Type"] = "application/json"
request["Accept"] = "application/json"
request.body = JSON.generate(payload)

response = http.request(request)

if response.is_a?(Net::HTTPSuccess)
  puts "Email request accepted."
  puts "HTTP #{response.code}"
  puts response.body unless response.body.nil? || response.body.empty?
else
  warn "Email request failed."
  warn "HTTP #{response.code} #{response.message}"
  warn response.body unless response.body.nil? || response.body.empty?
  exit 1
end

Run the script:

ruby send_email.rb

A successful HTTP response means the API accepted the request. It does not necessarily mean the recipient has already received the message in an inbox. Email delivery includes later stages such as provider processing, recipient-server acceptance, spam filtering, mailbox placement, bounces, and complaints. Treat the API response as a request-acceptance signal, then use delivery events and logs to observe what happened afterward.

How the Ruby request works

The sample is small, but each component has a specific operational purpose. Understanding those parts makes the code easier to adapt for receipts, password-reset emails, account alerts, invitations, and other transactional flows.

Loading secrets with dotenv

require "dotenv/load"

This line reads .env when the script runs locally. It is convenient for development, but it is not an alternative to a production secrets manager. The rest of the code accesses configuration through ENV, which works both with .env locally and with environment variables injected by your host in production.

Failing early when configuration is missing

api_key = ENV.fetch("VOLANEA_API_KEY")

ENV.fetch raises an error if a required variable is absent. That is preferable to silently making a request with a blank credential or sender address. A startup failure is generally easier to diagnose than a customer-facing email path that appears to run but sends nothing.

The same principle applies to the endpoint, sender, and recipient. A configuration error should be visible before your code attempts a network call.

Requiring HTTPS

unless uri.is_a?(URI::HTTPS)
  raise "VOLANEA_SEND_ENDPOINT must use HTTPS"
end

API credentials are secrets. Sending them over unencrypted HTTP would expose them to intermediaries on the network path. The guard makes an accidental http:// configuration fail immediately rather than transmitting a key insecurely.

Building the message payload

payload = {
  from: from,
  to: [to],
  subject: "Welcome to Example App",
  text: "Thanks for creating an account. Your email integration is working.",
  html: "<h1>Welcome to Example App</h1>"
}

The payload models the normal components of a transactional message: sender, recipient, subject, plain-text content, and HTML content. The exact JSON property names, recipient shape, and optional fields must match the Volanea send endpoint documented for your account. If the API reference uses a different field name or expects recipients in another structure, update the payload rather than trying to force an undocumented format.

Providing both text and html is a sound default for transactional messages. HTML gives you layout and visual hierarchy, while a text alternative remains useful for plain-text mail clients, accessibility workflows, security-conscious recipients, and situations where HTML rendering is disabled.

Sending JSON

request["Content-Type"] = "application/json"
request.body = JSON.generate(payload)

The Content-Type header tells the API how to interpret the request body. JSON.generate converts the Ruby hash into JSON safely, including escaping quotation marks, line breaks, and other special characters in dynamic values.

Do not construct a JSON string manually with interpolation such as "{\"subject\": \"#{subject}\"}". That approach can create invalid JSON or break when user-generated content contains quotes, backslashes, or newlines. Build a Ruby hash and let JSON.generate encode it.

Applying timeouts

http.open_timeout = 5
http.read_timeout = 15

Without timeouts, a stalled network connection can keep a web request or background worker blocked for too long. The values in the sample are starting points, not universal rules. A low-latency web request may need tighter limits, while a worker processing a backlog may allow longer timeouts.

Your retry policy must account for the fact that a timeout can occur after the provider has received the request but before your application receives the response. That ambiguity matters: blindly retrying can produce duplicate receipts, alerts, or verification emails unless the API supports an idempotency mechanism or your application has its own deduplication design.

Test the first transactional email safely

Before connecting the script to a live signup, billing, or password-reset flow, make the first request deliberately simple. Use an address you control, a verified sender identity, and a subject that clearly identifies the message as a test.

A practical test sequence is:

  1. Set VOLANEA_TO to a mailbox you control.
  2. Use a sender address from a domain configured for sending.
  3. Run ruby send_email.rb once.
  4. Record the HTTP status and response body without exposing the API key.
  5. Check the destination inbox, spam folder, and any delivery events or logs available in your Volanea account.
  6. Inspect the received message headers if you need to verify the sender identity and authentication results.

Avoid testing a production integration by repeatedly sending to arbitrary addresses. A transactional system should send because an application event occurred, not because a developer is manually retrying a script. Use a test recipient, a test environment, and an intentional test case.

Use realistic sender addresses

The from value should identify your application and be consistent with the domain you have configured. For example:

VOLANEA_FROM=Example App <notifications@example.com>

A recognizable display name helps recipients understand why they received the message. The mailbox portion should align with your domain-authentication setup and any sender-verification requirements. Do not use a public-mailbox address as a shortcut for production sending unless it is explicitly configured and appropriate for your organization.

Keep content transactional

This guide sends a welcome-style test message, but production transactional email should have a clear event-driven purpose. Common examples include:

  • Email-address verification links.
  • Password-reset instructions.
  • Login and security alerts.
  • Order receipts and invoices.
  • Appointment confirmations.
  • Team invitations.
  • Service-status notifications.

If you include marketing content in a message, make sure it is appropriate for the message type, recipient expectations, and applicable consent requirements. A password-reset email should remain focused on account recovery; it should not become a promotional campaign.

Adapt the sample for application code

A standalone script is a useful proof of connectivity. In an application, put the HTTP request behind a small service object so controllers, jobs, and domain logic do not all reimplement authentication and error handling.

Here is a compact example service class using the same standard-library approach:

require "json"
require "net/http"
require "uri"

class VolaneaMailer
  def initialize(
    api_key: ENV.fetch("VOLANEA_API_KEY"),
    endpoint: ENV.fetch("VOLANEA_SEND_ENDPOINT"),
    auth_scheme: ENV.fetch("VOLANEA_AUTH_SCHEME", "Bearer")
  )
    @api_key = api_key
    @uri = URI.parse(endpoint)
    @auth_scheme = auth_scheme

    raise ArgumentError, "endpoint must use HTTPS" unless @uri.is_a?(URI::HTTPS)
  end

  def send_transactional_email(from:, to:, subject:, text:, html:)
    request = Net::HTTP::Post.new(@uri.request_uri)
    request["Authorization"] = "#{@auth_scheme} #{@api_key}"
    request["Content-Type"] = "application/json"
    request["Accept"] = "application/json"
    request.body = JSON.generate(
      from: from,
      to: Array(to),
      subject: subject,
      text: text,
      html: html
    )

    http = Net::HTTP.new(@uri.host, @uri.port)
    http.use_ssl = true
    http.open_timeout = 5
    http.read_timeout = 15

    response = http.request(request)

    unless response.is_a?(Net::HTTPSuccess)
      raise "Volanea email request failed: HTTP #{response.code} #{response.body}"
    end

    response
  end
end

A caller can then send an account-verification message:

mailer = VolaneaMailer.new

mailer.send_transactional_email(
  from: ENV.fetch("VOLANEA_FROM"),
  to: "customer@example.net",
  subject: "Verify your email address",
  text: "Use the verification link in your account to confirm your email address.",
  html: "<p>Use the verification link in your account to confirm your email address.</p>"
)

This organization keeps your email provider integration in one place. If the documented endpoint, request schema, authentication configuration, or retry behavior changes later, you have a single service to update and test.

Reliability considerations for Ruby email sending

Sending an API request is only one part of a reliable transactional-email system. The application must also decide when to send, what to do after failure, how to avoid duplicates, and how to observe delivery outcomes.

Do not send before the business event is durable

Suppose a customer completes a purchase. If your application sends a receipt before the order transaction is committed, the email may go out even if the purchase record later rolls back. The customer then has a receipt for an order that does not exist.

A safer pattern is to persist the business event first and enqueue email work after the transaction is committed. Your background worker can read the durable record, render the content, and send the message. The exact mechanism varies by framework, but the principle is the same: do not let an email side effect get ahead of the system of record.

Design for retries without duplicate messages

Network errors, 5xx responses, worker restarts, and timeouts happen. Retrying can improve resilience, but retries must be intentional.

Before adding automatic retries, determine:

  • Which response classes are safe to retry according to the API documentation.
  • Whether the API supports a documented idempotency key or request identifier.
  • How your application records that a business event has already triggered an email.
  • How long a background job may retry before it requires manual investigation.
  • Whether a duplicate would be harmful for this message type.

For example, duplicate order receipts may be inconvenient, while duplicate password-reset emails can confuse users or create unnecessary support tickets. Build a durable event identifier into your application workflow and use any documented idempotency capability available from the email API.

Separate acceptance from delivery

An HTTP success response commonly indicates that the email API accepted the request. It is not proof of inbox placement. A message can later be deferred, bounced, rejected by the recipient server, filtered, or delivered to a spam folder.

For important flows, monitor both request outcomes and downstream email events. Use those signals to identify invalid addresses, authentication problems, unexpected bounce patterns, and recipient-domain issues. Do not mark a business action as fully complete solely because your Ruby process received a 2xx response.

Common errors

This section covers errors that commonly appear when sending email with Ruby through a REST API. When troubleshooting, capture the HTTP status code, response body, request ID if one is returned, and the event or application record that triggered the send. Never log the full API key or an unredacted authorization header.

Authentication failures: 401 or 403

A 401 Unauthorized or 403 Forbidden response usually points to a credential, permission, environment, or authentication-format problem.

Check the following:

  • Confirm VOLANEA_API_KEY is present in the process environment.
  • Ensure the key has not been truncated by shell quoting, deployment configuration, or a newline copied from a password manager.
  • Verify that the key is intended for the same Volanea account and environment as the configured endpoint.
  • Compare VOLANEA_AUTH_SCHEME and the authorization-header format with the current API reference.
  • Confirm that your application is not loading an old .env file or a stale deployment secret.
  • Rotate the key if it may have been exposed, then update the deployment secret.

To check that a variable exists without printing the secret, use:

ruby -e 'puts ENV.key?("VOLANEA_API_KEY") ? "API key is set" : "API key is missing"'

Do not debug authentication by printing the key to a terminal, log aggregator, exception tracker, or CI job output.

Wrong Content-Type: 400 or 415

If the API expects JSON, the request needs both a valid JSON body and the correct header:

request["Content-Type"] = "application/json"
request.body = JSON.generate(payload)

A common mistake is to send a Ruby hash directly as the body. Another is to set Content-Type to application/json but build malformed JSON manually. Use JSON.generate and inspect the non-secret payload shape during development.

If the documented Volanea send endpoint uses a content type or request format different from JSON, follow that reference exactly. Do not assume that every email endpoint accepts JSON just because many REST APIs do.

Invalid JSON fields or payload shape: 400 or 422

A request can be authenticated and still fail validation. Typical causes include a missing sender, malformed recipient, absent subject, unsupported content field, or a recipient value in the wrong structure.

The sample uses conventional fields such as from, to, subject, text, and html. Treat those as a starting structure, then compare them with the exact request schema in the current Volanea documentation. Pay special attention to whether the API expects one recipient string, an array of recipients, or recipient objects.

When testing, start with the smallest valid message: one verified sender, one recipient, one short subject, and both text and HTML content. Add optional features only after the basic send works.

Sender or domain is not ready

An API response may reject or fail to process a message if the sender address or domain does not meet the account’s sending requirements. This can happen when the domain is not authenticated, the sender identity differs from the configured identity, DNS records have not propagated, or a test environment is using a production sender incorrectly.

Use the sender address associated with your configured domain. Follow the domain-authentication instructions supplied for your account, including the exact DNS records and verification status shown there. Do not guess record names or values; DNS requirements are provider- and domain-specific.

Net::OpenTimeout, Net::ReadTimeout, or connection errors

These errors indicate that Ruby could not establish a connection or did not receive a response within the configured window. Causes can include DNS issues, outbound firewall restrictions, proxy configuration, a temporary network problem, or service unavailability.

First, verify that VOLANEA_SEND_ENDPOINT is an exact HTTPS URL copied from the API documentation. Then check that the deployment environment is allowed to make outbound HTTPS requests. If you add retries, use bounded retries with backoff and protect against duplicate sends as described earlier.

A timeout is ambiguous: the provider may have received the request even though your process did not receive the response. Do not automatically retry a customer-facing message indefinitely.

Async or background-job mistakes

The code in this guide is synchronous. http.request(request) blocks until a response arrives or Ruby raises an error. If you place it inside a background job, make sure the job framework records failures and retry attempts appropriately.

If you later switch to an asynchronous HTTP library, do not forget to wait for, await, join, or otherwise resolve the request according to that library’s API. A frequent async mistake is enqueuing work, allowing the process or job to finish, and assuming the message was sent without ever observing the network result.

In web applications, avoid sending nontrivial email work directly in the request-response path when it can be queued safely. A background job can reduce user-facing latency and give you a controlled place for retry logic, instrumentation, and failure handling.

Unexpected 2xx response but no email in the inbox

A successful API response is not an inbox guarantee. Check the message’s status in available sending logs or event data, then inspect the recipient mailbox’s spam or junk folder. Confirm that the recipient address is correct and that the sender domain is properly configured.

For important production flows, build monitoring around delivery and bounce events rather than relying only on the immediate Ruby response. A sudden increase in bounces, complaints, or recipient-domain failures is an operational signal that deserves investigation.

Security and content practices

Transactional email often contains account links, personal data, order details, and security-sensitive notifications. Treat email composition as part of your application’s security boundary.

Never insert untrusted user input into HTML without escaping it. If a user’s display name appears in an email, encode it for its destination context. HTML body content, URL query parameters, headers, and plain-text content all have different escaping and validation needs.

Do not place secrets, authentication tokens with excessive lifetime, full payment details, or sensitive internal data in email. Password-reset and login links should be short-lived, single-purpose, and generated according to your application’s security model. Email is not a secure vault, and recipients may forward, archive, or view messages on shared devices.

Keep transactional copy direct and recognizable. State what happened, what the recipient should do next, and how to get help. For example, an account-alert email should name the action and provide a legitimate support path rather than asking the recipient to reply with credentials.

Next steps

Once the basic Ruby send is working, make the integration operationally complete.

First, add webhooks or event handling where available in your Volanea configuration. Webhooks let your application receive event notifications after the initial send request, such as delivery-related outcomes and failures. Store event identifiers, verify webhook signatures according to the documented procedure, make handlers idempotent, and return success only after your application has safely accepted the event.

Next, move repeated message layouts into templates where your Volanea setup supports them. Templates help keep receipts, verification messages, alerts, and invitations consistent across services. Define a clear variable contract, validate required data before sending, render and test representative cases, and maintain a text alternative alongside HTML.

As volume grows, review transactional email pricing and sending costs alongside your retry policy, event retention, environment separation, and delivery-monitoring needs. The best sending architecture is not only one that can issue an API request; it is one that can explain what was sent, prevent duplicate customer communication, and surface failures quickly.

FAQ

Do I need a Ruby SDK to send email with Ruby?

No. Ruby can call an email REST API with built-in libraries such as Net::HTTP, URI, and JSON. This guide uses dotenv only to load local environment variables conveniently. Use the current Volanea API reference for the exact endpoint, authentication configuration, and message schema.

Where should I store VOLANEA_API_KEY?

Store it in environment variables or a secrets manager accessible only to trusted server-side code. Use .env only for local development, add it to .gitignore, and never expose the key in frontend code, repositories, screenshots, or logs.

Why does the example include both text and HTML?

A text alternative improves compatibility with plain-text mail clients and recipients who do not render HTML. HTML provides layout and branding, while text ensures the core message remains understandable without HTML rendering.

Does a successful API response mean the recipient received the email?

Not necessarily. It means the API request was accepted successfully. Delivery can still be affected by recipient-server responses, bounces, filtering, and mailbox placement. Use available event data or webhooks to monitor what happens after acceptance.

Should I send email directly from a Rails controller?

For low-risk testing, you can make a direct call. For production transactional flows, it is usually better to enqueue sending after the underlying business event is committed. A background job provides a safer place for retries, monitoring, and controlled failure handling.