Packages

SMTP email delivery channel for Raven alerts.

Current section

Files

Jump to
raven_integration_email lib integrations email.ex
Raw

lib/integrations/email.ex

defmodule Channels.Email do
@moduledoc """
Delivers notifications via email using SMTP.
Sends a plain-text email through any SMTP relay. Uses `Swoosh.Adapters.SMTP`
with per-delivery configuration so each channel instance can target a
different mail server without application-level reconfiguration.
## Params
* `:relay` — SMTP server hostname (required)
* `:port` — SMTP port; defaults to `587`
* `:username` — SMTP auth username (optional)
* `:password` — SMTP auth password (optional)
* `:tls` — TLS mode: `"always"`, `"never"`, or `"if_available"` (default)
* `:from` — sender email address (required)
* `:to` — recipient email address (required)
## Example
Via Mailpit (local test server, no auth):
%{relay: "mailpit.apps.svc.cluster.local", port: "1025",
tls: "never", from: "raven@example.com", to: "admin@example.com"}
If `:raven_core, :base_url` is configured, the body includes a link back
to the monitor's page on this instance's web UI.
"""
use CodeNameRaven.Channel, category: :email
import Swoosh.Email
alias CodeNameRaven.Channel.Notification
defmodule Mailer do
@moduledoc false
use Swoosh.Mailer, otp_app: :raven_integration_email
end
@impl true
def display_name, do: "Email"
@impl true
def params_template do
%{
relay: "",
port: "587",
username: "",
password: "",
tls: "if_available",
from: "",
to: ""
}
end
@impl true
def params_schema do
[
relay: [type: :string, required: true],
port: [type: :string, default: "587"],
username: [type: :string],
password: [type: :string],
tls: [type: {:in, ["always", "never", "if_available"]}, default: "if_available"],
from: [type: :string, required: true],
to: [type: :string, required: true]
]
end
@impl true
def deliver(%Notification{} = n, params) do
relay = str(params[:relay] || params["relay"])
from = str(params[:from] || params["from"])
to = str(params[:to] || params["to"])
with :ok <- require_param(relay, "relay"),
:ok <- require_param(from, "from"),
:ok <- require_param(to, "to") do
port = parse_int(params[:port] || params["port"], 587)
username = str(params[:username] || params["username"])
password = str(params[:password] || params["password"])
tls = parse_tls(params[:tls] || params["tls"])
email =
new()
|> from(from)
|> to(to)
|> subject(format_subject(n))
|> text_body(format_body(n))
smtp_config =
[adapter: Swoosh.Adapters.SMTP, relay: relay, port: port, tls: tls]
|> add_auth(username, password)
case Mailer.deliver(email, smtp_config) do
{:ok, _} -> :ok
{:error, reason} -> {:error, inspect(reason)}
end
end
end
# ---------------------------------------------------------------------------
# Formatting — public, directly exercised by CodeNameRaven.Channel.FormattingTest
# ---------------------------------------------------------------------------
@doc false
def format_subject(%Notification{} = n) do
"[Raven] #{n.monitor_name || n.monitor_id} is #{n.status}"
end
@doc false
def format_body(%Notification{} = n) do
[
"Monitor: #{n.monitor_name || n.monitor_id}",
"Status: #{n.status}",
previous_status_line(n),
down_duration_line(n),
"At: #{DateTime.to_iso8601(n.occurred_at)}",
view_link_line(n)
]
|> Enum.reject(&is_nil/1)
|> Enum.join("\n")
end
defp previous_status_line(%Notification{previous_status: nil}), do: nil
defp previous_status_line(%Notification{previous_status: prev}), do: "Previous status: #{prev}"
defp down_duration_line(%Notification{} = n) do
case Notification.format_duration_down(n) do
nil -> nil
duration -> "Down for: #{duration}"
end
end
defp view_link_line(%Notification{monitor_id: monitor_id}) do
case Application.get_env(:raven_core, :base_url) do
base_url when is_binary(base_url) and base_url != "" ->
"View: #{base_url}/monitors/#{monitor_id}"
_ ->
nil
end
end
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
defp add_auth(config, nil, _password), do: config
defp add_auth(config, username, password), do: config ++ [username: username, password: password || ""]
defp require_param(nil, name), do: {:error, "#{name} is required"}
defp require_param(_, _), do: :ok
defp parse_tls("always"), do: :always
defp parse_tls("never"), do: :never
defp parse_tls(_), do: :if_available
defp str(nil), do: nil
defp str(""), do: nil
defp str(v), do: v
defp parse_int(nil, default), do: default
defp parse_int(v, _) when is_integer(v), do: v
defp parse_int(v, default) when is_binary(v) do
case Integer.parse(v) do
{n, _} -> n
:error -> default
end
end
defp parse_int(_, default), do: default
end