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

118 lines
8.4 KiB
Markdown

# Mail Operations
This guide describes the production Mailjet boundary for outbound transactional mail and inbound webhook authentication. It is intentionally redacted: use environment key names, diagnostic codes, queue state, request correlation ids, and provider status classes only. Do not paste provider secrets, webhook payloads, customer message contents, tenant identifiers, or real attempt ids into docs, logs, CI output, or support notes.
For the current security contract, see `docs/mail-auth.md`. For the runtime configuration pattern and redacted environment validation rules, see `docs/configuration.md`.
## Outbound Mailjet environment keys
Outbound delivery uses Mailjet through the configured mail provider boundary. The API and worker runtimes must receive these values from the deployment secret store, CI secret store, or local placeholder configuration used only for development:
- `MAILJET_API_KEY`: Mailjet API token used only for outbound API calls.
- `MAILJET_FROM_EMAIL`: default sender address from a verified Mailjet sender domain.
- `MAILJET_FROM_NAME`: optional sender display name for transactional messages.
Treat `MAILJET_API_KEY` and `MAILJET_FROM_EMAIL` as a required pair whenever Mailjet delivery is enabled. If one key is missing or invalid, fail configuration with a redacted diagnostic that names the missing key and status code only. Do not log the rejected value or embed sample secret-shaped values in documentation.
`MAILJET_WEBHOOK_SECRET` is not an outbound delivery token. It authenticates inbound webhooks and must not be reused as `MAILJET_API_KEY`.
## Verified sender and domain setup
Before enabling production delivery, configure the Mailjet account with a verified sender domain and approve the sender address used by `MAILJET_FROM_EMAIL`. Keep domain authentication records, provider dashboard state, and bounce/complaint status in the provider console rather than copying raw provider records into application logs.
Operational checks may record redacted facts such as:
- sender domain verification status;
- sender address key name, not the production value;
- Mailjet response status or error category;
- request correlation id for the application action that requested mail.
If Mailjet rejects delivery because the sender domain is not verified, update the provider configuration first. Do not work around the error by switching to an unapproved sender value in source code or support notes.
## Queue-backed transactional mail flow
Transactional mail is queue-backed when runtime work is delegated to workers. The application should enqueue a mail job with normalized metadata, then the worker should call Mailjet using the configured provider options. Queue diagnostics should stay at the operational level: job status, retry count, last error code, provider status class, and correlation id are safe; message contents and tenant ids are not.
Expected flow:
1. API or application logic validates the request and creates a mail intent without logging message bodies.
2. The mail intent is placed on the queue with redacted metadata and an internal correlation id.
3. The persistent `worker-mail` service (`WORKER_QUEUE_GROUP=mail`) reads the queue item and sends through Mailjet using `MAILJET_API_KEY`, `MAILJET_FROM_EMAIL`, and `MAILJET_FROM_NAME`.
4. Provider responses are summarized by status or diagnostic code, then persisted or logged only in redacted form.
5. Retries use queue state and provider error class, not copied message content or raw Mailjet payloads.
See `docs/ops/worker-queues.md` for the canonical Coolify split. In staging and
production, `worker-mail` must run as a separate persistent service with
`TRANSACTIONAL_MAIL_OUTBOX_REQUIRED=true`; otherwise future queued mail can
remain pending until the persistent mail worker is restored.
This document does not require live Mailjet calls, Redis access, or browser/E2E execution. Verification for this task is static documentation validation only.
## Inbound webhook signing
Inbound Mailjet webhooks must be authenticated before any attempt lookup, tenant lookup, database write, or queue enqueue. Configure the API runtime with:
- `MAILJET_WEBHOOK_SECRET`: signing secret from the Mailjet webhook settings for the endpoint.
This secret is separate from the outbound `MAILJET_API_KEY`. The API key authorizes calls to Mailjet; the webhook signing secret proves that an inbound request was sent by Mailjet for the configured endpoint. Conflating the two values weakens both boundaries and should be treated as invalid configuration.
Expected signature headers are accepted in either standard or forwarded naming form:
- `webhook-id` or `x-webhook-id`
- `webhook-timestamp` or `x-webhook-timestamp`
- `webhook-signature` or `x-webhook-signature`
All required header classes must be present, fresh for the replay window, and matched against the exact raw body bytes. Verification must use the raw body as received on the wire, not a parsed JSON object, pretty-printed body, normalized whitespace, or reserialized payload. Middleware, framework helpers, and tests around this route must preserve raw-body access for the verifier.
On missing, malformed, stale, or mismatched signature material, return a redacted authentication failure such as `401 invalid_webhook_signature` before any downstream work occurs.
## Rotation steps
Rotate Mailjet credentials using a planned deployment window and key-specific rollback notes:
1. Confirm which credential is rotating: outbound `MAILJET_API_KEY` or inbound `MAILJET_WEBHOOK_SECRET`.
2. Create the replacement credential in Mailjet or the provider dashboard without copying the value into tickets or docs.
3. Update the runtime secret store entry for the matching environment key.
4. Redeploy or restart the affected API and worker runtimes so all instances read the same current value.
5. For outbound mail, send a provider-console test or run an approved operational smoke path and record only status codes and correlation ids.
6. For inbound webhooks, send a Mailjet test webhook and confirm signed requests pass while invalid signatures still fail with a redacted code.
7. Remove superseded values from secret stores after every runtime has switched.
Do not rotate by placing old and new secrets in source code. If dual-secret webhook rotation is needed later, implement and document an explicit previous/current-secret verifier before changing these steps.
## Troubleshooting
Use safe diagnostics only. Good troubleshooting artifacts include request correlation ids, queue status, retry count, high-level Mailjet status or error category, missing environment key names, missing header class names, and redacted verifier output.
Common outbound issues:
- `MAILJET_API_KEY` is missing, expired, or scoped incorrectly.
- `MAILJET_FROM_EMAIL` is absent or not associated with a verified Mailjet sender domain.
- The worker is not processing the queue, so the mail job remains pending or retrying.
- Mailjet returns a provider status indicating rate limiting, rejection, bounce, or sender-domain configuration failure.
Common inbound webhook issues:
- `MAILJET_WEBHOOK_SECRET` is missing from the API runtime secret store.
- Mailjet is configured with a different signing secret than the runtime.
- A proxy or middleware stripped `webhook-*` / `x-webhook-*` headers.
- The raw body was parsed or transformed before signature verification.
- The webhook timestamp is outside the replay window.
When sharing diagnostics, summarize failures as key names and codes, for example `missing required env key MAILJET_WEBHOOK_SECRET` or `401 invalid_webhook_signature`. Never include the secret value, raw body, signature header value, message body, tenant id, or real attempt id.
## Redaction constraints
The following values must not appear in docs, logs, screenshots, CI output, issue trackers, support notes, or copied provider responses:
- Mailjet API keys;
- Mailjet webhook signing secrets;
- raw webhook bodies;
- `webhook-signature` or `x-webhook-signature` values;
- transactional message contents;
- tenant ids or customer identifiers;
- real attempt ids from production traffic;
- provider dashboard secrets or token fragments.
Prefer placeholders such as `<mailjet-api-key>`, `<mailjet-webhook-signing-secret>`, `<request-correlation-id>`, and `<redacted-provider-error>`. If an unsafe value is accidentally captured, rotate the affected secret when applicable, remove the artifact from the shared surface, and replace it with a redacted summary.