docs(mail): record that the email.bitborg.se move is blocked at the provider #380

Sammanfogat
supernaut sammanfogade 1 incheckning från docs/mail-domain-step-zero in i main 2026-08-05 14:11:42 +00:00
Ägare

What happened

The email.bitborg.se move was planned on "DKIM and DMARC are already published and the Sweego credentials are unchanged, so there is no dashboard work." DNS was indeed published — the DKIM CNAME resolves, with the same key as the old domain — and the move still failed.

Sweego does not authorise email.bitborg.se, so every portal email was rejected from the moment step 2 deployed. Step 2 has been reverted (bitborg-web #197); step 3 was never applied.

Measured from inside the running container with the portal's own SWEEGO_API_KEY — same request shape, only the From domain differing:

From result
no-reply@mail.gitborg.se HTTP 200 — accepted, transaction id returned
no-reply@email.bitborg.se HTTP 401 {"detail":"Unauthorized"}

Published DNS is not provider authorisation

That is the whole lesson. The pre-flight that passed had checked the DKIM CNAME and the DMARC record — which was never the question. A verified sending identity on the account is, and it has no representation in DNS or in this repo.

Two things made it hide:

  • Sweego answers 401, not 422, for an unauthorised sender, so it reads as a credential fault rather than a domain one. The first single-domain probe returned a bare 401, equally consistent with a wrong key or a malformed request.
  • Only sending as both domains with the same key in the same breath isolates the domain as the variable.

The "credentials are unchanged" assumption may itself be the error: the key can be bound to a sending identity, so a new domain may need it re-scoped rather than reused.

So the procedure gains a step 0

Prove the provider accepts a send as the new domain — with that two-domain differential — before anything else moves. The runnable check is recorded beside mail_allowed_sender_domains.

What the guard cannot do

senderAllowed() compares the sender against what this repo declares, and this repo had declared the new domain allowed. So the guard passed and the provider refused. The runbook already described that mechanism, but attached it only to a permanently widened allowlist — it applies equally to a correctly widened one whose new domain the provider does not accept.

State left behind

  • Step 1 stays applied and is now inert — it permits a domain nothing sends as. Flagged in place so the next reader knows whether it is waiting to be finished or to be dropped.
  • Step 3's blast radius is recorded from the dry-run (Forgejo, portal and Alertmanager restarts; six changed tasks across both hosts) so it is not discovered later.
  • §5 is marked blocked in the switchover register.

Unblocking

Verify email.bitborg.se in the Sweego dashboard, re-run the two-domain send, require 2xx on the new domain, then re-land step 2.

## What happened The `email.bitborg.se` move was planned on *"DKIM and DMARC are already published and the Sweego credentials are unchanged, so there is no dashboard work."* DNS was indeed published — the DKIM CNAME resolves, with the same key as the old domain — **and the move still failed.** Sweego does not authorise `email.bitborg.se`, so every portal email was rejected from the moment step 2 deployed. Step 2 has been reverted (bitborg-web #197); step 3 was never applied. Measured from inside the running container with the portal's own `SWEEGO_API_KEY` — same request shape, only the From domain differing: | From | result | | --- | --- | | `no-reply@mail.gitborg.se` | **HTTP 200** — accepted, transaction id returned | | `no-reply@email.bitborg.se` | **HTTP 401** `{"detail":"Unauthorized"}` | ## Published DNS is not provider authorisation That is the whole lesson. The pre-flight that passed had checked the DKIM CNAME and the DMARC record — which was never the question. A **verified sending identity on the account** is, and it has no representation in DNS or in this repo. Two things made it hide: - **Sweego answers `401`, not `422`,** for an unauthorised sender, so it reads as a credential fault rather than a domain one. The first single-domain probe returned a bare `401`, equally consistent with a wrong key or a malformed request. - Only sending as **both** domains with the same key in the same breath isolates the domain as the variable. The "credentials are unchanged" assumption may itself be the error: the key can be bound to a sending identity, so a new domain may need it re-scoped rather than reused. ## So the procedure gains a step 0 Prove the provider accepts a send as the new domain — with that two-domain differential — **before anything else moves.** The runnable check is recorded beside `mail_allowed_sender_domains`. ## What the guard cannot do `senderAllowed()` compares the sender against what **this repo** declares, and this repo had declared the new domain allowed. So the guard passed and the provider refused. The runbook already described that mechanism, but attached it only to a *permanently widened* allowlist — it applies equally to a correctly widened one whose new domain the provider does not accept. ## State left behind - **Step 1 stays applied and is now inert** — it permits a domain nothing sends as. Flagged in place so the next reader knows whether it is waiting to be finished or to be dropped. - **Step 3's blast radius** is recorded from the dry-run (Forgejo, portal and Alertmanager restarts; six changed tasks across both hosts) so it is not discovered later. - §5 is marked **blocked** in the switchover register. ## Unblocking Verify `email.bitborg.se` in the Sweego dashboard, re-run the two-domain send, require **2xx on the new domain**, then re-land step 2.
supernaut lade till 1 incheckning 2026-08-05 14:09:36 +00:00
docs(mail): record that the email.bitborg.se move is blocked at the provider
Alla kontroller lyckades
ci / ci (pull_request) Successful in 1m30s
8574e1e4cd
The move was planned on "DKIM and DMARC are already published and the Sweego credentials
are unchanged, so there is no dashboard work". DNS was indeed published — the DKIM CNAME
resolves, with the same key as the old domain — and the move still failed. Sweego does not
authorise email.bitborg.se, so every portal email was rejected from the moment step 2
deployed. Step 2 has been reverted (bitborg-web #197) and step 3 was never applied.

Measured from inside the running container with the portal's own SWEEGO_API_KEY, same
request shape, only the From domain differing:

    no-reply@mail.gitborg.se   -> HTTP 200, accepted (transaction id returned)
    no-reply@email.bitborg.se  -> HTTP 401 {"detail":"Unauthorized"}

PUBLISHED DNS IS NOT PROVIDER AUTHORISATION. The pre-flight that passed had checked the
DKIM CNAME and the DMARC record, which was never the question — a verified sending identity
on the account is, and it has no representation in DNS or in this repo.

Two things made it hide. Sweego answers 401 rather than 422 for an unauthorised sender, so
it reads as a credential fault rather than a domain one, and the first single-domain probe
returned a bare 401 that was consistent with a wrong key or a malformed request. Only
sending as BOTH domains with the same key in the same breath isolates the domain. The
"credentials are unchanged" assumption may itself be the error: the key can be bound to a
sending identity, so a new domain may need it re-scoped rather than reused.

So the procedure gains a STEP 0 — prove the provider accepts a send as the new domain,
with that two-domain differential, before anything else moves. The runnable check is
recorded beside mail_allowed_sender_domains.

Also states plainly what the allowlist guard cannot do. senderAllowed() compares the
sender against what THIS REPO declares, and this repo had declared the new domain allowed,
so the guard passed and the provider refused. The runbook already described that mechanism
but attached it only to a permanently-widened allowlist; it applies equally to a correctly
widened one whose new domain the provider does not accept.

Step 1 stays applied and is now inert: it permits a domain nothing sends as. Flagged in
place so the next reader knows whether it is waiting to be finished or to be dropped.

Records step 3's blast radius from the dry-run (Forgejo, portal and Alertmanager restarts,
six changed tasks across both hosts) so that is not discovered later, and marks §5 blocked
in the switchover register.
supernaut sammanfogade incheckning de96cf972a till main 2026-08-05 14:11:42 +00:00
Logga in för att delta i denna konversation.
Inga granskare
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-infra!380
Ingen beskrivning angiven.