Elixir SDK
The official Elixir SDK for Veil Mail with idiomatic tagged tuples, pattern matching, and Phoenix integration.
- Idiomatic tagged tuple responses (ok/error)
- Struct-based client (no global Application config)
- Typed error struct with pattern matching
- Constant-time HMAC-SHA256 webhook verification
- Built on Req + Jason (modern Elixir HTTP)
- Elixir 1.14+, full @spec typespecs
Installation
Add veilmail to your dependencies in mix.exs:
def deps do
[
{:veilmail, "~> 0.1.0"}
]
endQuick Start
client = VeilMail.client("veil_live_xxxxx")
{:ok, email} = VeilMail.Emails.send(client, %{
from: "hello@yourdomain.com",
to: ["user@example.com"],
subject: "Hello from Elixir!",
html: "<h1>Welcome!</h1>"
})
IO.inspect(email)Configuration
Configure the client with keyword options:
client = VeilMail.client("veil_live_xxxxx",
base_url: "https://custom-api.example.com",
timeout: 10_000
)Resources
The SDK exposes the same resources as the Node.js SDK:
| Module | Description |
|---|---|
VeilMail.Emails | Send, batch send, list, get, cancel, update emails |
VeilMail.Domains | Create, verify, update, list, delete domains |
VeilMail.Templates | Create, update, preview, list, delete templates |
VeilMail.Audiences | Manage audiences and subscribers |
VeilMail.Campaigns | Create, schedule, send, pause, resume, cancel campaigns |
VeilMail.Webhooks | Manage webhook endpoints, test, rotate secrets |
VeilMail.Topics | Manage subscription topics and preferences |
VeilMail.Properties | Manage contact property definitions and values |
Send Emails
# Simple send
{:ok, email} = VeilMail.Emails.send(client, %{
from: "hello@yourdomain.com",
to: ["user@example.com"],
subject: "Hello!",
html: "<p>Hello World!</p>",
tags: ["welcome"]
})
# With template
{:ok, email} = VeilMail.Emails.send(client, %{
from: "hello@yourdomain.com",
to: ["user@example.com"],
templateId: "tmpl_xxx",
templateData: %{name: "Alice"}
})
# Batch send (up to 100)
{:ok, result} = VeilMail.Emails.send_batch(client, [
%{from: "hi@yourdomain.com", to: ["user1@example.com"], subject: "Hi", html: "<p>Hi!</p>"},
%{from: "hi@yourdomain.com", to: ["user2@example.com"], subject: "Hi", html: "<p>Hi!</p>"}
])Subscriber Management
# Add a subscriber
{:ok, subscriber} = VeilMail.Audiences.add_subscriber(client, "audience_xxxxx", %{
email: "user@example.com",
firstName: "Alice",
lastName: "Smith"
})
# List subscribers
{:ok, result} = VeilMail.Audiences.list_subscribers(client, "audience_xxxxx",
limit: "50",
status: "active"
)
# Export as CSV
{:ok, csv} = VeilMail.Audiences.export_subscribers(client, "audience_xxxxx")Error Handling
Use pattern matching on the VeilMail.Error struct:
case VeilMail.Emails.send(client, params) do
{:ok, email} ->
IO.puts("Sent: #{email["id"]}")
{:error, %VeilMail.Error{type: :authentication, message: msg}} ->
IO.puts("Invalid API key: #{msg}")
{:error, %VeilMail.Error{type: :pii_detected, pii_types: types}} ->
IO.puts("PII detected: #{inspect(types)}")
{:error, %VeilMail.Error{type: :rate_limit, retry_after: retry}} ->
IO.puts("Rate limited, retry after #{retry} seconds")
{:error, %VeilMail.Error{type: :validation, message: msg}} ->
IO.puts("Validation: #{msg}")
{:error, %VeilMail.Error{message: msg}} ->
IO.puts("Error: #{msg}")
endWebhook Verification
The SDK provides VeilMail.Webhook for constant-time HMAC-SHA256 verification:
defmodule MyAppWeb.WebhookController do
use MyAppWeb, :controller
@webhook_secret "whsec_xxxxx"
def handle(conn, _params) do
{:ok, body, conn} = Plug.Conn.read_body(conn)
signature = Plug.Conn.get_req_header(conn, "x-signature-hash") |> List.first("")
case VeilMail.Webhook.verify(body, signature, @webhook_secret) do
{:ok, payload} ->
process_event(payload)
send_resp(conn, 200, "OK")
{:error, _reason} ->
send_resp(conn, 401, "Invalid signature")
end
end
defp process_event(%{"type" => "email.delivered", "data" => data}) do
IO.inspect(data, label: "Delivered")
end
defp process_event(%{"type" => "email.bounced", "data" => data}) do
IO.inspect(data, label: "Bounced")
end
defp process_event(_event), do: :ok
endNo Global State
The client is a struct, not Application-level config. This means you can have multiple clients with different API keys, tests run concurrently without conflicts, and configuration is explicit at every call site. Store the client in your application's supervision tree or pass it through your context.
Required Scopes
The Elixir SDK uses the same API scopes as the Authentication system. See the Node.js SDK documentation for the full scope reference.