Moving Desktop Crash Report Delivery Behind an API Boundary
The real problem was not the SMTP error itself. It was release clients depending on mail configuration baked into the shipped application.
- SMTP
- API
- WPF
- .NET
- Release Engineering
What the problem looked like
The visible symptom was an SMTP authentication failure. Release users could no longer send crash reports or suggestion messages from the application.
At first glance, that looks like a normal mail settings problem. But the deeper issue was that the delivery configuration lived inside the shipped client. Once that configuration became outdated, every installed release inherited the same broken behavior.
Why the DLL boundary was the real issue
When SMTP details are embedded directly in client-side desktop code, mail delivery becomes tied to the exact version already deployed to users. If credentials, auth rules, sender policy, or provider-side behavior changes, old releases keep trying to send with stale assumptions.
- Release users cannot benefit from a server-side mail fix unless they install a new client build.
- SMTP credentials and provider behavior become part of the release compatibility surface.
- Crash and suggestion delivery become harder to debug because the problem sits inside the distributed app.
- A mail provider change should not require updating every installed client.
Why an API became necessary
Moving to an API gave the application a stable contract: the desktop client only needs to submit a report payload, while the server owns SMTP, credentials, retries, and any future provider changes.
That was the key reason for the change. The goal was not to make the system bigger. The goal was to stop mail delivery rules from being locked into old release binaries.
The desktop client should only submit a report
After the change, the client no longer needs to know SMTP host, port, TLS, or auth details. It just sends the report body, recipients, and optional attachments to one server endpoint.
csharp
MailService.cs
The important change is that the released client now talks to one API contract instead of owning the mail transport details itself.
using MultipartFormDataContent requestContent = new();
requestContent.Add(new StringContent(subject, Encoding.UTF8), "Subject");
requestContent.Add(new StringContent(body, Encoding.UTF8), "Body");
foreach (string address in toAddresses)
{
requestContent.Add(new StringContent(address, Encoding.UTF8), "ToAddresses");
}
using HttpRequestMessage request = new(HttpMethod.Post, reportApiUrl)
{
Content = requestContent
};
request.Headers.Add("X-Api-Key", reportApiKey);
using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken);The API owns mail delivery
On the server side, the endpoint receives the report payload and forwards it through the real mail infrastructure. That means SMTP settings can change without touching the desktop release.
csharp
ReportsController.cs
This keeps the client transport simple and makes SMTP a server concern instead of a release concern.
[HttpPost("submit")]
public async Task<IActionResult> Submit([FromForm] ReportRequestForm request, CancellationToken cancellationToken)
{
await _reportEmailService.SendAsync(new ReportEmailMessage
{
Subject = request.Subject,
Body = request.Body,
ToAddresses = request.ToAddresses,
Attachments = request.Attachments
}, cancellationToken);
return Ok(new { message = "Report submitted successfully." });
}A stable contract also needs a stable hostname
There was one more release risk to remove. Pointing the desktop client at a fixed server IP would make a future server migration another client-update problem: installed versions would keep calling the old address.
A dedicated hostname adds the missing layer of indirection. The client keeps a stable endpoint while DNS can be redirected to a new server without rebuilding the desktop application. The configured value is also validated as an absolute URI before a request is created.
DNS does not hide breaking API changes. The route and payload contract still need to remain compatible, but the physical server address is no longer part of the released client contract.
csharp
ReportDeliveryClient.cs
The sample uses a placeholder hostname and validates it before sending; no project endpoint or credentials are exposed.
private const string ReportApiUrl =
"https://reports.example.com/api/reports/submit";
private static Uri GetReportEndpoint()
{
if (!Uri.TryCreate(ReportApiUrl, UriKind.Absolute, out Uri? endpoint))
{
throw new InvalidOperationException("The report API URL is invalid.");
}
return endpoint;
}
using var request = new HttpRequestMessage(HttpMethod.Post, GetReportEndpoint())
{
Content = requestContent
};What changed after that decision
After moving the flow behind an API and addressing it through a stable hostname, crash and suggestion delivery stopped being tightly coupled to stale mail configuration or one server IP inside old release clients.
That was the real win. SMTP auth errors were only the symptom. The actual fix was moving unstable delivery and infrastructure details out of the shipped client boundary.
Share