# `PhoenixKit.Integrations.Validators`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.13.8/lib/phoenix_kit/integrations/validators.ex#L1)

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.

# `amazon_bedrock`

```elixir
@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`

```elixir
@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`

```elixir
@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.

# `object_storage`

```elixir
@spec object_storage(map()) :: :ok | {:error, String.t()}
```

Validates S3-compatible object storage credentials against the storage API
itself (AWS S3, Cloudflare R2, Backblaze B2, Tigris, or a self-hosted
endpoint).

Calls `ListBuckets` — an account-level operation that needs no bucket name,
so it works before the operator has picked one. Built via `ExAws.S3`
(already a dependency); the `region`/`endpoint` config shape mirrors
`PhoenixKit.Modules.Storage.Providers.S3`'s `aws_config/1`, which builds the
same request for the same four providers.

Without a custom endpoint, the standard regional AWS host is used —
including the `.amazonaws.com.cn` host for the China partition. A GovCloud
environment that requires the FIPS endpoint (`s3-fips.<region>.amazonaws.com`)
rather than the standard one should set it explicitly via `endpoint`.

# `smtp`

```elixir
@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`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
