Files
HiveGrid-Wireframes/hivegrid-cloud/docs/mail-auth.md
T

5.8 KiB

Mail Authentication

This document records the inbound and outbound mail-authentication contract for the API and worker runtimes. It is intentionally operational and redacted: use placeholder names, environment key names, and status codes only; do not paste API keys, webhook secrets, webhook bodies, message contents, tenant identifiers, or real attempt ids into this file or related diagnostics.

Outbound Mailjet delivery

Transactional email delivery uses Mailjet (Send API v3.1) through the mail provider boundary. Mailjet authenticates outbound requests with HTTP Basic Auth using a public/secret API key pair. Outbound delivery requires the key pair and verified sender settings documented in docs/configuration.md:

  • MAILJET_API_KEY is the Mailjet public API key (Basic-Auth username).
  • MAILJET_SECRET_KEY is the Mailjet private/secret API key (Basic-Auth password).
  • MAILJET_FROM_EMAIL is the default sender address from a verified Mailjet sender domain.
  • MAILJET_FROM_NAME is an optional sender display name.

Treat MAILJET_API_KEY, MAILJET_SECRET_KEY, and MAILJET_FROM_EMAIL as an outbound delivery set. If any one is configured without the others, the runtime should report a redacted configuration error by key name rather than attempting delivery or logging raw values.

Inbound Mailjet webhook authentication

The Mailjet provider webhooks — the email-sequence event endpoint POST /v1/email-sequences/mailjet/webhooks, the inbound parse endpoint POST /v1/support/inbound-email, and the legacy retry endpoint POST /v1/events/webhooks/attempts/:id — are public webhook boundaries and must authenticate inbound Mailjet attempts before looking up a record or enqueueing work.

Mailjet does not sign event/parse webhooks (there is no HMAC signature on the payload, unlike Svix-style schemes). The supported authentication is HTTP Basic Auth over HTTPS: configure the Mailjet event callback URL as https://mailjet:SECRET@host/... so Mailjet sends an Authorization: Basic base64("mailjet:SECRET") header.

Configure the API runtime with:

  • MAILJET_WEBHOOK_SECRET: the shared secret used as the HTTP Basic-Auth password. The username may be anything (the verifier ignores it and checks the password in constant time).

This secret is separate from the MAILJET_API_KEY/MAILJET_SECRET_KEY outbound pair. The outbound keys authorize outbound API calls to Mailjet; the webhook secret authenticates inbound webhook payloads. Do not reuse outbound values for the webhook secret, do not store any of them in source control, and do not expose any value in logs, health responses, OpenAPI examples, or support tickets.

Expected credentials

Mailjet webhook requests are verified against the Basic-Auth credentials in the Authorization header:

  • Authorization: Basic <base64(username:MAILJET_WEBHOOK_SECRET)>

The header is required. The password portion must match MAILJET_WEBHOOK_SECRET in a constant-time comparison. Missing, malformed, or mismatched credentials must be rejected with 401 invalid_webhook_signature before any database lookup or queue enqueue. When MAILJET_WEBHOOK_SECRET itself is not configured, the runtime returns 500 webhook_signing_not_configured.

Transport requirement

Because Basic Auth carries the secret in the request, the callback must be served over HTTPS so the credential is not exposed in transit. Avoid placing the secret in the URL query string (it would land in access and proxy logs); the credential travels in the Authorization header derived from the configured callback URL.

Rotation notes

Rotate MAILJET_WEBHOOK_SECRET using a deployment window that keeps Mailjet and the API runtime in sync:

  1. Update the Mailjet event callback URL with the new Basic-Auth password for the target endpoint.
  2. Update the API runtime secret store with the new MAILJET_WEBHOOK_SECRET value.
  3. Redeploy or restart API instances so every instance reads the same current secret.
  4. Send a Mailjet test event and confirm authenticated requests reach the existing endpoint behavior.
  5. Remove any superseded secret from secret stores after all API instances have switched.

If zero-downtime dual-secret rotation is later required, implement and document an explicit previous/current-secret verifier contract before changing operational steps. Until then, avoid staggered deployments where old and new API instances validate different webhook secrets for the same Mailjet endpoint.

Troubleshooting 401 invalid_webhook_signature

Use only redacted facts when investigating webhook auth failures. Safe diagnostics include request correlation id, HTTP status, error code, environment key names, and the failure reason class (missing-credentials, malformed-credentials, credentials-mismatch). Unsafe diagnostics include the webhook secret, raw body fragments, the Authorization header value, attempt ids from customer traffic, API keys, or message content.

Common causes:

  • MAILJET_WEBHOOK_SECRET is missing from the API runtime secret store (returns 500 webhook_signing_not_configured).
  • The Mailjet callback URL is configured with a different Basic-Auth password than the API runtime secret.
  • A proxy, middleware, or test helper stripped or rewrote the Authorization header before it reached the API.
  • The callback URL is configured without credentials, so no Authorization header is sent.
  • The request was sent to a different webhook endpoint than the one whose secret was configured.

Expected behavior during failures: the API returns a request-correlated error envelope with 401 invalid_webhook_signature (or 500 webhook_signing_not_configured when the secret is unset), does not return or log secret/credential/body fragments, and does not query records or enqueue work. Once a request presents valid Mailjet Basic-Auth credentials, the endpoint preserves the existing behavior and any downstream diagnostics.