Eerste commit
This commit is contained in:
+228
@@ -0,0 +1,228 @@
|
||||
# E-mail: hoe MijnBesluit post verstuurt
|
||||
|
||||
Zonder e-mail werkt MijnBesluit niet. Inloggen gaat met een code per mail, en uitnodigingen aan
|
||||
familie gaan per mail. Elke storing hier is dus een storing in het hart van de applicatie.
|
||||
|
||||
## Hoe het nu loopt
|
||||
|
||||
Sinds september 2026 gaat alle uitgaande post via de **eigen Kerio Connect** op dezelfde server
|
||||
als de website. Het domein mijnbesluit.nl staat daar als domein ingericht, met de mailboxen
|
||||
`noreply@mijnbesluit.nl` en `info@mijnbesluit.nl`.
|
||||
|
||||
```
|
||||
MijnBesluit (website + SocketServer)
|
||||
-> 127.0.0.1:25 (Kerio Connect, zelfde machine)
|
||||
-> poort 25 naar buiten -> Gmail en de rest
|
||||
```
|
||||
|
||||
De afzender is `noreply@mijnbesluit.nl`. Dat mag nu, want het domein staat op onze eigen server;
|
||||
bij Solcon kon het niet (zie "Wat er al een keer misging").
|
||||
|
||||
## Waar de instellingen staan
|
||||
|
||||
In `C:\MijnBesluitData\appsettings.secrets.json` op de server, in het blok `Smtp`. Dat bestand
|
||||
wordt gelezen door **zowel de website als de SocketServer**, zodat er één plek is:
|
||||
|
||||
```json
|
||||
"Smtp": {
|
||||
"Host": "127.0.0.1",
|
||||
"Port": 25,
|
||||
"UseStartTls": false,
|
||||
"Username": "",
|
||||
"Password": "",
|
||||
"FromAddress": "noreply@mijnbesluit.nl",
|
||||
"FromName": "MijnBesluit",
|
||||
"WriteToDiskInsteadOfSending": false,
|
||||
"RedirectAllTo": ""
|
||||
}
|
||||
```
|
||||
|
||||
### Waarom gebruikersnaam en wachtwoord leeg zijn
|
||||
|
||||
Kerio staat relayen toe voor de groep "Local clients", en `127.0.0.1` zit daarin. Post die van
|
||||
deze server zelf komt hoeft dus niet in te loggen. Dat is niet alleen eenvoudiger maar ook beter:
|
||||
er staat geen mailwachtwoord meer in dit bestand.
|
||||
|
||||
**Vul hier niet alsnog een wachtwoord in.** Klopt het niet, dan krijg je geen foutmelding maar
|
||||
traagheid - zie hieronder.
|
||||
|
||||
Na een wijziging moet je herstarten; de instellingen worden één keer bij het opstarten gelezen
|
||||
en daarna vastgehouden, ondanks `reloadOnChange`:
|
||||
|
||||
```powershell
|
||||
Restart-Service MijnBesluitSocket
|
||||
Restart-WebAppPool mijnbesluit
|
||||
```
|
||||
|
||||
### Waarom `127.0.0.1` en niet `localhost`
|
||||
|
||||
`localhost` lost Windows eerst op naar het IPv6-adres `::1`. Luistert Kerio daar niet, dan wacht
|
||||
.NET het verbindingstijdslimiet af voordat hij IPv4 probeert. Het resultaat: de mail komt wél
|
||||
aan, maar het antwoord aan de app duurt meer dan tien seconden en het proefscript geeft een
|
||||
time-out. Met een hard IPv4-adres is het binnen een seconde klaar.
|
||||
|
||||
**Dit geldt net zo goed voor de database.** MariaDB luistert standaard alleen op 127.0.0.1, dus
|
||||
`Server=localhost` in de connectiereeks kost bij elke nieuwe verbinding dezelfde verloren
|
||||
seconden - merkbaar als de eerste bezoeker na een herstart van de app-pool lang moet wachten.
|
||||
Zet ook daar `Server=127.0.0.1`. Meet het verschil zo:
|
||||
|
||||
```powershell
|
||||
Measure-Command { .\tools\SocketServer-proef.ps1 } # raakt de database niet
|
||||
Measure-Command { .\tools\SocketServer-proef.ps1 -Email ... -Nieuw } # raakt hem wel
|
||||
```
|
||||
|
||||
### Waarom geen TLS op die stap
|
||||
|
||||
Kerio presenteert op 587 nog een zelfondertekend certificaat. `System.Net.Mail.SmtpClient`
|
||||
weigert dat botweg (`The remote certificate is invalid according to the validation procedure`,
|
||||
met `RemoteCertificateNameMismatch, RemoteCertificateChainErrors`), en de controle uitzetten is
|
||||
in een applicatie die wachtwoorden bewaart het verkeerde ruilmiddel.
|
||||
|
||||
Over loopback is dat te verdedigen: het pakket verlaat de machine niet. Maar het hoort tijdelijk
|
||||
te zijn - zie "Wat hier nog niet goed aan is".
|
||||
|
||||
Let op: `SmtpClient` kan geen implicit TLS. Poort 465 werkt dus nooit; dat moet 587 met STARTTLS
|
||||
zijn zodra er een geldig certificaat is.
|
||||
|
||||
## De DNS voor mijnbesluit.nl
|
||||
|
||||
Bij Strato. Alles hieronder is september 2026 ingericht; daarvóór had het domein helemaal geen
|
||||
TXT-records.
|
||||
|
||||
| Naam | Type | Waarde |
|
||||
|---|---|---|
|
||||
| `mijnbesluit.nl` | A | 85.215.171.61 |
|
||||
| `mijnbesluit.nl` | MX | `mail.kwik-bit.nl`, prioriteit hoog |
|
||||
| `mijnbesluit.nl` | TXT | `v=spf1 ip4:85.215.171.61 ~all` |
|
||||
| `mail._domainkey.mijnbesluit.nl` | TXT | de DKIM-sleutel die Kerio toont |
|
||||
| `_dmarc.mijnbesluit.nl` | TXT | `v=DMARC1; p=none; rua=mailto:info@mijnbesluit.nl` |
|
||||
|
||||
Aandachtspunten:
|
||||
|
||||
* In het MX-record hoort een **naam**, geen IP-adres. Een MX naar `85.215.171.61.` wordt door
|
||||
mailservers die zich aan de regels houden geweigerd.
|
||||
* Strato's eigen SPF- en DMARC-knoppen staan op "geen", zodat er geen tweede record naast het
|
||||
onze komt. Twee SPF-records betekent dat geen van beide werkt.
|
||||
* De reverse lookup van 85.215.171.61 geeft `mail.kwik-bit.nl`, en die naam wijst terug naar
|
||||
hetzelfde IP. Die heen-en-terugcontrole is wat Gmail eist; hij hoeft niet bij het
|
||||
afzenderdomein te horen. De Internet hostname in Kerio moet gelijk zijn aan die PTR.
|
||||
|
||||
### Bekend probleem: ns3 loopt achter
|
||||
|
||||
De twee nameservers van Strato geven niet hetzelfde antwoord: `ns4.stratoserver.net` heeft het
|
||||
DKIM-record, `ns3.stratoserver.net` valt terug op een wildcard-CNAME naar `mijnbesluit.nl`.
|
||||
DKIM slaagt daardoor ongeveer de helft van de keren. SPF staat op het domein zelf en heeft er
|
||||
geen last van, en SPF alleen is genoeg voor DMARC-uitlijning - de post komt dus gewoon aan.
|
||||
|
||||
Controleren:
|
||||
|
||||
```powershell
|
||||
$ns = (Resolve-DnsName mijnbesluit.nl -Type NS -Server 8.8.8.8).NameHost
|
||||
foreach ($s in $ns) {
|
||||
"--- $s"
|
||||
Resolve-DnsName mail._domainkey.mijnbesluit.nl -Type TXT -Server $s -ErrorAction SilentlyContinue |
|
||||
Select-Object Type, @{n='Waarde';e={ if ($_.Strings) { ($_.Strings -join '').Substring(0,40) } else { $_.NameHost } }}
|
||||
}
|
||||
```
|
||||
|
||||
Staat ns3 er over een paar dagen nog steeds anders in, dan is dat een melding aan Strato:
|
||||
*"`mail._domainkey.mijnbesluit.nl` geeft op ns3 een wildcard-CNAME en op ns4 het TXT-record."*
|
||||
|
||||
## Testen
|
||||
|
||||
Van binnen naar buiten, zodat je weet wáár het misgaat:
|
||||
|
||||
```powershell
|
||||
# 1. de hele keten: socket, database, mailserver
|
||||
.\tools\SocketServer-proef.ps1 -Email iemand@example.nl -Nieuw
|
||||
|
||||
# 2. alleen de mailserver
|
||||
$c = Get-Credential
|
||||
Send-MailMessage -SmtpServer mail.kwik-bit.nl -Port 587 -UseSsl -Credential $c `
|
||||
-From "noreply@mijnbesluit.nl" -To "..." -Subject "Proef" -Body "Proef"
|
||||
```
|
||||
|
||||
`-Nieuw` vraagt een registratiecode in plaats van een inlogcode, zodat je naar een adres kunt
|
||||
testen waar nog geen account bij hoort. Er wordt pas een account aangemaakt als de code wordt
|
||||
ingevuld.
|
||||
|
||||
Komt de mail aan bij Gmail, open hem dan via *Origineel weergeven*: daar staat of SPF, DKIM en
|
||||
DMARC geslaagd zijn. Dat is de enige echte controle.
|
||||
|
||||
## Wat er al een keer misging
|
||||
|
||||
### 0. Een voorbeeldtekst als wachtwoord kost negen seconden per inlogcode
|
||||
|
||||
Het langste dat dit project ons heeft gekost, en het stond al die tijd in het instellingenbestand:
|
||||
|
||||
```json
|
||||
"Password": "het wachtwoord van die mailbox",
|
||||
```
|
||||
|
||||
Dat is de voorbeeldtekst uit de handleiding, nooit vervangen. Het gevolg is níet een foutmelding.
|
||||
Kerio weigert de aanmelding, .NET probeert het opnieuw met een andere methode, en valt daarna
|
||||
terug op versturen zonder aanmelden - wat mag, want het IP-adres staat in "Local clients". De
|
||||
mail komt dus gewoon aan. Alleen duurt hij **negen seconden**, elke keer, en die staat de
|
||||
gebruiker bij het inloggen uit te zitten.
|
||||
|
||||
Wat het zo lastig maakte om te vinden: alles wat je van buitenaf meet is snel. Kerio doet 5 ms,
|
||||
met en zonder inloggen. Maar dat meet je met Windows PowerShell, en dat is .NET Framework 4.8 -
|
||||
een andere implementatie van `SmtpClient` dan de .NET 8 waar de applicatie op draait, die andere
|
||||
keuzes maakt bij het aanmelden. Appels met peren.
|
||||
|
||||
Lessen voor de volgende keer:
|
||||
|
||||
* Controleer het instellingenbestand **als eerste**, niet als laatste.
|
||||
* Windows PowerShell is geen goede proef voor .NET-gedrag. Gebruik `pwsh` als je het na wilt doen.
|
||||
* De tijdmeting in `SmtpEmailSender` splitst het wachten in "wachten op een vrije draad" en
|
||||
"het SMTP-gesprek zelf". Die twee getallen wezen het aan; laat ze staan.
|
||||
|
||||
De applicatie waarschuwt nu bij het opstarten als het SMTP-wachtwoord spaties bevat - een echt
|
||||
wachtwoord doet dat zelden en een voorbeeldzin altijd.
|
||||
|
||||
### 1. De afzender moet bij het account horen
|
||||
|
||||
Solcon weigerde elk afzenderadres dat niet bij het inlogaccount hoorde:
|
||||
|
||||
```
|
||||
Mailbox unavailable. The server response was: Verification failed for <noreply@mijnbesluit.nl>
|
||||
```
|
||||
|
||||
Dat is met de eigen mailserver verdwenen.
|
||||
|
||||
### 2. Elke mail heeft een Message-ID nodig
|
||||
|
||||
```
|
||||
550-5.7.1 Messages missing a valid Message-ID header are not accepted
|
||||
```
|
||||
|
||||
.NET zet die koptekst niet zelf, en niet elke mailserver vult hem aan. `SmtpEmailSender` zet hem
|
||||
er daarom zelf op; zie `Berichtkenmerk` in de Application-laag.
|
||||
|
||||
### 3. Het domein moet zeggen wie er namens hem mag versturen
|
||||
|
||||
```
|
||||
550-5.7.26 Gmail requires all senders to authenticate with either SPF or DKIM
|
||||
```
|
||||
|
||||
Vandaar de records hierboven.
|
||||
|
||||
## Wat hier nog niet goed aan is
|
||||
|
||||
**Kerio heeft geen geldig certificaat.** Zelfondertekend, op de verkeerde naam. Daardoor moet de
|
||||
applicatie over loopback zonder TLS praten, krijgt elke mailclient een waarschuwing, en kunnen
|
||||
andere mailservers de TLS-verbinding weigeren. De oplossing is een Let's Encrypt-certificaat voor
|
||||
`mail.kwik-bit.nl` met **win-acme** op die server - die kan via IIS op poort 80 valideren, en na
|
||||
afloop een script draaien dat het certificaat in Kerio zet. Kerio's eigen Let's Encrypt-knop
|
||||
werkt hier niet, want die wil poort 80 zelf hebben en die is van IIS.
|
||||
|
||||
Daarna kan het `Smtp`-blok terug naar `mail.kwik-bit.nl` op poort 587 met `UseStartTls: true`.
|
||||
|
||||
**Je ziet niet of een mail aankomt.** De applicatie weet alleen of Kerio hem heeft aangenomen.
|
||||
Wordt hij daarna alsnog geweigerd, dan komt die melding als bounce terug op `noreply@` en ziet de
|
||||
gebruiker alleen "de code is verstuurd" terwijl er niets aankomt. De DMARC-rapporten naar
|
||||
`info@mijnbesluit.nl` geven daar wel zicht op, maar pas achteraf en per dag.
|
||||
|
||||
**`p=none` in DMARC.** Dat is de juiste stand om mee te beginnen: je krijgt rapporten zonder dat
|
||||
er post geweigerd wordt. Gaat het een paar weken goed, dan kan het naar `p=quarantine` en het
|
||||
SPF-record van `~all` naar `-all`.
|
||||
Reference in New Issue
Block a user