How to Monitor Swoosh with Vigilmon
Swoosh is Elixir's email library — it composes emails with a unified API and delivers them through adapters for Mailgun, SendGrid, SMTP, SES, and more. But email delivery is a silent failure mode: if your Swoosh adapter loses its connection to the email provider, transactional emails (password resets, invoices, alerts) stop going out with no immediate error in your application logs.
This tutorial wires up layered monitoring for Swoosh-powered email:
- A health endpoint that tests Swoosh adapter connectivity
- HTTP uptime monitoring with Vigilmon
- Heartbeat monitors that detect delivery queue stalls
- Adapter-specific health checks for Mailgun and SendGrid
- Alerts when the email pipeline degrades
Why Monitor Swoosh?
| Signal | What it catches | |---|---| | Adapter connectivity | SMTP/API authentication failures | | Delivery queue depth | Emails queuing but not delivering | | Bounce rate | Invalid recipient addresses or domain issues | | Provider API health | Mailgun/SendGrid outages | | Test email delivery | End-to-end delivery verification |
Users notice failed emails seconds after they happen (missing password reset, undelivered invoice). Monitoring catches failures before users report them.
Step 1: Add a Swoosh Health Check Endpoint
Create a health check that verifies the Swoosh adapter can connect to the email provider:
# lib/my_app/health/swoosh_health.ex
defmodule MyApp.Health.SwooshHealth do
alias Swoosh.Email
alias MyApp.Mailer
@doc """
Verifies the Swoosh adapter is configured and reachable.
Sends a test email to a monitored inbox when in production.
"""
def check do
case check_adapter_config() do
:ok -> check_adapter_connectivity()
error -> error
end
end
defp check_adapter_config do
config = Application.get_env(:my_app, Mailer, [])
cond do
is_nil(config[:adapter]) ->
{:error, "No Swoosh adapter configured"}
config[:adapter] == Swoosh.Adapters.Test ->
# Test adapter is always "healthy" — skip connectivity check
:ok
true ->
:ok
end
end
defp check_adapter_connectivity do
# Use Swoosh's built-in deliver_now! to test the adapter
# with a minimal email to a monitoring inbox
test_email =
Email.new()
|> Email.to(Application.get_env(:my_app, :health_check_email, "health@example.com"))
|> Email.from({"Vigilmon Health", "noreply@yourapp.com"})
|> Email.subject("[HEALTH CHECK] Swoosh adapter test")
|> Email.text_body("Vigilmon Swoosh health check — ignore this email.")
case Mailer.deliver(test_email) do
{:ok, _} -> {:ok, %{status: "ok", adapter: adapter_name()}}
{:error, reason} -> {:error, %{status: "error", reason: inspect(reason)}}
end
end
defp adapter_name do
Application.get_env(:my_app, Mailer, [])
|> Keyword.get(:adapter, :unknown)
|> Module.split()
|> List.last()
end
end
Wire it into a controller:
# lib/my_app_web/controllers/health_controller.ex
def swoosh(conn, _params) do
case MyApp.Health.SwooshHealth.check() do
{:ok, result} ->
json(conn, result)
{:error, reason} when is_map(reason) ->
conn |> put_status(503) |> json(reason)
{:error, reason} ->
conn |> put_status(503) |> json(%{status: "error", reason: reason})
end
end
Add the route:
scope "/health", MyAppWeb do
get "/swoosh", HealthController, :swoosh
end
Note: In production, limit the frequency of test email delivery — use the heartbeat approach in Step 4 instead of checking on every request.
Step 2: Add Telemetry for Delivery Tracking
Swoosh emits telemetry events on delivery. Attach a handler to track success and failure rates:
# lib/my_app/swoosh_telemetry.ex
defmodule MyApp.SwooshTelemetry do
require Logger
def setup do
:telemetry.attach_many(
"swoosh-delivery-handler",
[
[:swoosh, :deliver, :stop],
[:swoosh, :deliver, :exception]
],
&handle_event/4,
nil
)
end
def handle_event([:swoosh, :deliver, :stop], measurements, metadata, _config) do
duration_ms = System.convert_time_unit(measurements.duration, :native, :millisecond)
status = if metadata[:result] == :ok, do: "success", else: "failure"
Logger.info("[Swoosh] Email delivery #{status} in #{duration_ms}ms")
:telemetry.execute(
[:my_app, :email, :delivery],
%{duration: duration_ms},
%{status: status, adapter: adapter_name()}
)
end
def handle_event([:swoosh, :deliver, :exception], _measurements, metadata, _config) do
Logger.error("[Swoosh] Email delivery exception: #{inspect(metadata[:reason])}")
:telemetry.execute(
[:my_app, :email, :delivery],
%{duration: 0},
%{status: "exception", adapter: adapter_name()}
)
end
defp adapter_name do
Application.get_env(:my_app, MyApp.Mailer, [])
|> Keyword.get(:adapter, :unknown)
|> Module.split()
|> List.last()
end
end
Attach in application.ex:
def start(_type, _args) do
MyApp.SwooshTelemetry.setup()
# ...
end
Step 3: Add Vigilmon Uptime Monitoring
For production environments where the health check doesn't send a live test email, monitor the adapter configuration endpoint:
- Log in to vigilmon.online and click Add Monitor.
- Set Type to
HTTP / HTTPS. - Enter your health endpoint:
https://yourapp.com/health/swoosh. - Set Check interval to
5 minutes(avoid excessive test emails). - Set Expected HTTP status to
200. - Under Advanced, add a Response body contains check:
"status":"ok". - Click Save.
Step 4: Add a Heartbeat for the Email Delivery Queue
The most reliable way to monitor email delivery is to actually deliver emails and check they arrive. Use a heartbeat that delivers a test email to a monitored inbox:
# lib/my_app/workers/email_health_worker.ex
defmodule MyApp.Workers.EmailHealthWorker do
use GenServer
@interval :timer.minutes(5)
@vigilmon_heartbeat_url "https://vigilmon.online/api/heartbeat/YOUR_HEARTBEAT_ID"
def start_link(_), do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)
def init(_) do
schedule()
{:ok, %{failure_count: 0}}
end
def handle_info(:check, state) do
new_state =
case deliver_test_email() do
{:ok, _} ->
ping_vigilmon()
%{state | failure_count: 0}
{:error, reason} ->
require Logger
Logger.error("[EmailHealthWorker] Test email delivery failed: #{inspect(reason)}")
%{state | failure_count: state.failure_count + 1}
end
schedule()
{:noreply, new_state}
end
defp deliver_test_email do
alias Swoosh.Email
email =
Email.new()
|> Email.to("health-check@yourmonitoredinbox.com")
|> Email.from({"Health Check", "noreply@yourapp.com"})
|> Email.subject("[MONITOR] Email pipeline health check")
|> Email.text_body("Swoosh delivery pipeline is healthy.")
MyApp.Mailer.deliver(email)
end
defp ping_vigilmon do
# Use a simple HTTP call to ping the Vigilmon heartbeat
:httpc.request(:get, {String.to_charlist(@vigilmon_heartbeat_url), []}, [], [])
end
defp schedule, do: Process.send_after(self(), :check, @interval)
end
To create the heartbeat in Vigilmon:
- Click Add Monitor → Cron / Heartbeat.
- Set the expected ping interval to
5 minutes. - Copy the generated heartbeat URL into
@vigilmon_heartbeat_url.
If your Mailgun or SendGrid API key expires, or the SMTP credentials rotate, the heartbeat stops and Vigilmon alerts you within 5–10 minutes.
Step 5: Monitor the Email Provider API Directly
Add a secondary Vigilmon monitor pointing to your email provider's status page API:
For Mailgun:
- Add Monitor → HTTP/HTTPS →
https://status.mailgun.com/api/v2/status.json - Check interval: 5 minutes
- Response body contains:
"indicator":"none"
For SendGrid:
- Add Monitor → HTTP/HTTPS →
https://status.sendgrid.com/api/v2/status.json - Check interval: 5 minutes
- Response body contains:
"indicator":"none"
Step 6: Set Up Alerts
- Go to Alert Channels → Add Channel.
- Choose Slack, Email, or PagerDuty.
- Set alert thresholds: 1 failure for the heartbeat (email failures are immediately user-visible).
- Assign the channel to your Swoosh health monitor and delivery heartbeat.
For password-reset or billing email flows, use a dedicated PagerDuty escalation so on-call engineers are paged immediately when the email pipeline fails.
Key Metrics to Watch
| Metric | Vigilmon feature | What to alert on |
|---|---|---|
| Adapter connectivity | HTTP monitor on /health/swoosh | Any 5xx or timeout |
| Delivery queue health | Heartbeat monitor | Missing ping for > 10 min |
| Email provider uptime | HTTP monitor on provider status | Non-"none" indicator |
| Response time | Response time chart | Delivery latency > 10s |
| SSL certificate | Cert expiry monitor | Expires in < 14 days |
Conclusion
Email failures are high-impact and user-visible within seconds. Swoosh makes email composition and delivery elegant in Elixir — but without monitoring, adapter failures and provider outages go undetected until users report missing emails. A health endpoint, a delivery heartbeat, and a provider status monitor give you three independent signals to catch email pipeline failures early.
Get started free at vigilmon.online.