# Send email from Ruby on Rails

Five steps: add a small client, create a test key, send to the sandbox from a controller, read the log, then verify a domain from a rake task so you can send to anyone.

## 1. Install

No SDK or gem needed. The API is plain JSON over HTTPS, so `Net::HTTP` and `JSON` from the standard library are all it takes. A short module wraps the four calls this page uses; a non-2xx status carries `{ "error": { "code", "message" } }`, which becomes an `Avelto::Error`.

**Ruby**

```ruby
# app/services/avelto.rb
require "net/http"
require "json"

module Avelto
  API = "https://api-staging.avelto.dev"

  class Error < StandardError
    attr_reader :status, :code

    def initialize(status, code, message)
      super(message)
      @status = status
      @code = code
    end
  end

  def self.request(method, path, body = nil)
    uri = URI("#{API}#{path}")
    klass = method == :post ? Net::HTTP::Post : Net::HTTP::Get
    req = klass.new(uri)
    req["Authorization"] = "Bearer #{ENV.fetch('AVELTO_API_KEY')}"
    req["Content-Type"] = "application/json"
    req.body = body.to_json if body

    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
      http.request(req)
    end
    data = JSON.parse(res.body)

    unless res.is_a?(Net::HTTPSuccess)
      error = data["error"]
      raise Error.new(res.code.to_i, error["code"], error["message"])
    end
    data
  end

  def self.send_email(email)
    request(:post, "/v1/emails", email)
  end

  def self.get_email(id)
    request(:get, "/v1/emails/#{id}")
  end

  def self.create_domain(name)
    request(:post, "/v1/domains", { name: name })
  end

  def self.get_domain(id)
    request(:get, "/v1/domains/#{id}")
  end
end
```

## 2. Create an API key

[Sign in](/login), open **API keys** in the dashboard and create a **test** key. It starts with `av_test_`. Export it so the app can read it:

**shell**

```bash
export AVELTO_API_KEY=av_test_...
```

> **Sandbox rules.** Test keys never deliver anything; they run the pipeline and record events. The sandbox sender `you@sandbox.avelto.dev` only delivers to your account's verified owner email and to the simulator addresses `delivered@`, `bounced@` and `complained@sandbox.avelto.dev`. Anything else is refused with `403 sandbox_recipient_not_allowed`. To send to anyone, verify a domain (step 5).

## 3. Send your first email

A controller action that sends and returns the API's answer, or the error envelope with the same status.

**Ruby**

```ruby
# app/controllers/emails_controller.rb
class EmailsController < ApplicationController
  skip_forgery_protection # so you can try it with curl

  def create
    email = Avelto.send_email(
      from: "you@sandbox.avelto.dev",
      to: "delivered@sandbox.avelto.dev",
      subject: "Hello from Avelto",
      text: "It works."
    )
    render json: email, status: :created # { "id": "9c1f4a52-..." }
  rescue Avelto::Error => e
    render json: { error: { code: e.code, message: e.message } }, status: e.status
  end
end
```

**Ruby**

```ruby
# config/routes.rb
post "/send", to: "emails#create"
get "/emails/:id", to: "emails#show"
```

Run `bin/rails server` and call the action:

**shell**

```bash
curl -X POST http://localhost:3000/send
```

The API answers `201 Created` with the email id, and the action returns the same:

```http
HTTP/1.1 201 Created
Content-Type: application/json

{ "id": "9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10" }
```

> **Retrying safely.** Send an `Idempotency-Key` header (any unique string, such as your order id) with every `POST /v1/emails`. If the request times out or comes back `429`, `502`, `503` or `504`, wait a moment and send it again unchanged with the same key: the API returns the original email id instead of sending twice. See [Idempotency](/docs/send-email).

## 4. Check the log

A `show` action fetches the email by id. `status` moves from `queued` to `sent` to `delivered`, and `events` records each step: `email.queued`, `email.sent`, `email.delivered`.

**Ruby**

```ruby
def show
  email = Avelto.get_email(params[:id])
  render json: {
    status: email["status"], # "queued", then "sent", then "delivered"
    events: email["events"].map { |e| e["type"] }
  }
end
```

**shell**

```bash
curl http://localhost:3000/emails/9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10
```

## 5. Verify a domain

Adding a domain is a one-off task, so it fits a rake task. Publish the DNS records it prints (three DKIM CNAMEs, an SPF TXT and a DMARC TXT) at your DNS provider; the task polls `GET /v1/domains/:id`, which re-checks DNS on every call, until `status` is `verified`. Use a subdomain such as `mail.acme.com`.

**Ruby**

```ruby
# lib/tasks/avelto.rake
namespace :avelto do
  desc "Add a sending domain and wait until it is verified"
  task :verify, [:name] => :environment do |_t, args|
    domain = Avelto.create_domain(args[:name])

    domain["dns_records"].each do |r|
      puts [r["type"], r["name"], r["value"], "(#{r['purpose']})"].join("\t")
    end

    # Publish the records, then poll. GET re-checks DNS on every call.
    status = domain["status"]
    while status == "pending"
      sleep 30
      status = Avelto.get_domain(domain["id"])["status"]
    end

    puts status # "verified" or "failed"
  end
end
```

**shell**

```bash
bin/rails "avelto:verify[mail.acme.com]"
```

Once the domain is verified, switch `AVELTO_API_KEY` to a live key (`av_live_`) and change `from` in the controller to an address on it, such as `hello@mail.acme.com`. Nothing else changes.

## Next

- [Send email](/docs/send-email): every field, attachments, tags, scheduling and idempotency.
- [Webhooks](/docs/webhooks): get events pushed to your app.
- [Test mode](/docs/test-mode): test keys, the sandbox sender and the simulator addresses.

---

Rendered page: https://staging.avelto.dev/docs/quickstart/rails
