Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

duva-rails

Action Mailer delivery method for Duva, the transactional email API hosted in Canada. Your mailers, deliver_later jobs and Devise emails go out through Duva with no change to your mailer code.

Requires Ruby 3.3+ and Rails (Action Mailer) 7.2 to 8.x. Built on the official duva-mail library.

Install

# Gemfile
gem "duva-rails"

You also need a Duva API key and a verified domain (documentation).

Configure

# config/environments/production.rb
config.action_mailer.delivery_method = :duva
config.action_mailer.duva_settings = {
  api_key: Rails.application.credentials.dig(:duva, :api_key), # or ENV["DUVA_API_KEY"]
  domain: "example.com"                                         # or ENV["DUVA_DOMAIN"]
}
config.action_mailer.raise_delivery_errors = true
# app/mailers/application_mailer.rb
default from: "Example <notifications@example.com>" # the address must belong to the Duva domain

Optional settings: base_url (default https://api.duva.ca), timeout (seconds, default 10), max_retries (default 2). api_key and domain fall back to the DUVA_API_KEY and DUVA_DOMAIN environment variables.

What is sent

Mail Duva
From, To, Cc, Bcc, Reply-To from, to, cc, bcc, reply_to (an address only in smtp_envelope_to is sent as Bcc)
text and html parts, or a single-part body text, html
attachments[...], attachments.inline[...] attachments (inline ones keep their content_id)
List-Unsubscribe, List-Unsubscribe-Post, List-Id, In-Reply-To, References, Auto-Submitted, Precedence, Importance, Feedback-ID, and X-* headers headers
X-Duva-Tags, X-Duva-Metadata, X-Duva-Track, X-Idempotency-Key read by this gem, never sent (see below)

Duva-specific options are set as headers in the mailer:

def welcome(user)
  headers["X-Duva-Tags"] = "welcome, onboarding"               # tags
  headers["X-Duva-Metadata"] = { user_id: user.id }.to_json    # metadata (JSON object)
  headers["X-Duva-Track"] = "opens, clicks"                    # tracking (needs an HTML part)
  headers["X-Idempotency-Key"] = "welcome-#{user.id}"          # see "Idempotency"
  mail(to: user.email, subject: "Welcome")
end

Notes:

  • Other headers are not forwarded (Duva rejects them), nor are X-Duva*, X-Kumo*, X-Tenant* and X-Campaign*.
  • A display name Duva refuses (it contains @, quotes, <>, control characters or an encoded word =?…?=) is dropped; the address is kept.
  • Delivery is asynchronous: a successful deliver_now means Duva accepted the message, not that it was delivered. Use Duva's webhooks or events for the outcome.
  • A Duva error (Duva::Error and its subclasses, from the duva-mail library) is raised from deliver_now, and retried by Active Job in deliver_later according to your job configuration.

Idempotency

A retried deliver_later job builds a new message, so a retry after a network timeout could send twice. Set a stable key for it (an order ID, for example) with the X-Idempotency-Key header. Duva ignores a second message with the same key.

Testing your application

Use ActionMailer::Base.delivery_method = :test in your tests as usual. To exercise this gem without a network, give it the fake transport of the client library:

transport = Duva::TestTransport.new { |_config, _spec| Duva::TestTransport.response("id" => "msg_...", "status" => "queued") }
ActionMailer::Base.duva_settings = { api_key: "dv_test", domain: "example.com", transport: transport }
# ... deliver ...
transport.requests.last.body # what would have been sent

License

MIT.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages