PhoenixKit.Integrations.Validators (phoenix_kit v2.4.0)

Copy Markdown View Source

Real connection checks for providers that cannot be validated with a simple authenticated HTTP GET.

PhoenixKit.Integrations falls through to :ok for any provider that declares no validation. For the e-mail providers that meant "Test Connection" verified nothing: the connection was stamped "connected" without a single byte leaving the box, so an operator who pasted a wrong key or a bad SMTP password saw a green check and then a failing send.

A check that always says yes is worse than no check — but so is one that says no when the configuration is fine. These validators are therefore careful in both directions:

  • a relay that authenticates by IP and offers no AUTH verb passes (the credentials are simply unused — sending works, so the check is green);
  • SES credentials scoped to ses:SendEmail only — the least-privilege policy AWS itself recommends — pass, even though they cannot read the send quota.

Every check runs through PhoenixKit.Integrations.Probe, which bounds it with a hard deadline and isolates it from the caller — the libraries underneath bound neither themselves nor their crashes, and both call sites are LiveView callbacks.

Summary

Functions

Validates an Amazon Bedrock API key against the Bedrock control plane.

Validates AWS SES credentials against the SES API itself.

Validates a Brevo API key against the account endpoint, and reports the remaining credits.

Validates an SMTP relay by opening a real session and authenticating.

Validates a Telegram bot token against the Bot API's getMe.

Functions

amazon_bedrock(data)

@spec amazon_bedrock(map()) :: :ok | {:ok, String.t()} | {:error, String.t()}

Validates an Amazon Bedrock API key against the Bedrock control plane.

Lists foundation models in the configured region — the cheapest call that proves both the key and the region at once. Bedrock long-term API keys authenticate with a plain Bearer header (no SigV4), the same header the OpenAI-compatible runtime endpoint accepts — a green check proves the completions path can authenticate IN THIS REGION; an AI endpoint must point its base URL at the same region for that guarantee to carry over.

aws_ses(data)

@spec aws_ses(map()) :: :ok | {:ok, String.t()} | {:error, String.t()}

Validates AWS SES credentials against the SES API itself.

Asks SES for the account's send quota — the cheapest call that proves the credentials are real. Built as a raw ExAws.Operation.Query, so it needs no ex_aws_ses dependency.

The endpoint host is passed explicitly rather than left to ExAws, whose region resolution is a hard-coded prefix allowlist (~r/^(us|eu|af|ap|sa|ca|me)-.../) that silently yields no host at all for newer regions such as il-central-1 — while the send path (Swoosh.Adapters.AmazonSES) simply interpolates the region and works. Validate and send must resolve the same endpoint.

brevo_api(data)

@spec brevo_api(map()) :: :ok | {:ok, String.t()} | {:error, String.t()}

Validates a Brevo API key against the account endpoint, and reports the remaining credits.

Brevo authenticates with a bare api-key header (no Bearer prefix), and GET /v3/account doubles as the cheapest authenticated call and the only one that tells the operator anything beyond "the key works" — the plan's remaining send credits, which is exactly what silently runs out mid-campaign.

smtp(data)

@spec smtp(map()) :: :ok | {:ok, String.t()} | {:error, String.t()}

Validates an SMTP relay by opening a real session and authenticating.

The connection options come from PhoenixKit.Mailer.SmtpTransport.config/1, so the check exercises exactly the transport a real send uses — one source of truth, no drift between "tested" and "sent".

Forcing AUTH for the probe is deliberate: with gen_smtp's :if_available the AUTH exchange is attempted but its failure is tolerated, so a wrong password would still open a session and the check would pass. The operator's auth setting is honored in one direction only — never stays never (there are no credentials to prove), while if_available is upgraded to always for the probe so a bad password fails the check. A connection with no username and no password stays never for the same reason as an explicit never. A relay that advertises no AUTH verb at all is a different case, and is treated as a pass — see the module doc.

telegram(data)

@spec telegram(map()) :: :ok | {:ok, String.t()} | {:error, String.t()}

Validates a Telegram bot token against the Bot API's getMe.

Telegram embeds the token in the URL path (/bot<token>/getMe) rather than a header, so it can't use the generic header-based check. getMe is the cheapest authenticated call, and its reply carries the bot's username — a useful confirmation the operator connected the bot they meant to.