# 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 ``` 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`.