自定义批量邮件软件切换Office 365后SendMailAsync偶发无返回求助
Hey there, let's dig into why your email sending is hanging randomly after switching to Office 365, and break down potential issues in your code.
First, Let's Flag Code Risks
1. async void in StartNextEmail
Using async void for methods that aren't direct event handlers is a common pitfall. In your case, when StartNextEmail is called from the timer tick, any exceptions that occur after the await sc.SendMailAsync(mm) line (or inside that method) might not be caught by your catch blocks. This can lead to silent hangs or unobserved task failures that don't crash your program but stop execution cold.
Fix this by changing StartNextEmail to return async Task, then adjust the timer handler to await it:
private async Task StartNextEmail() { // ... your existing code ... } private async void timer_Tick(object sender, EventArgs e) { await StartNextEmail().ConfigureAwait(false); }
This ensures exceptions from StartNextEmail are properly routed to your error-handling logic.
2. Missing Timeout & Cancellation
The legacy System.Net.Mail.SmtpClient has a critical limitation: its Timeout property doesn't apply to asynchronous methods like SendMailAsync. If Office 365's SMTP server stops responding (due to rate limits, silent connection drops, or server delays), your task can hang indefinitely—exactly the behavior you're seeing.
Add a manual timeout workaround using Task.WhenAny:
// Inside SendEmailAsync var sendTask = sc.SendMailAsync(mm); var timeoutTask = Task.Delay(TimeSpan.FromMinutes(1)); // Adjust based on your needs var completedTask = await Task.WhenAny(sendTask, timeoutTask); if (completedTask == timeoutTask) { progressViewModel.Log($"Timeout sending to {recipient.Address}"); throw new TimeoutException("Email send timed out"); } await sendTask; // Surface any exceptions from the actual send operation
Why SendMailAsync Hangs with Office 365
1. Outdated SmtpClient Implementation
Microsoft has marked System.Net.Mail.SmtpClient as obsolete because it relies on legacy SMTP protocols and has poor async support. Office 365 uses modern, stricter SMTP standards, and the old client doesn't handle edge cases like silent connection drops or rate-limiting gracefully. It often fails to surface errors, leaving tasks stuck waiting for a response that never comes.
2. Office 365 SMTP Restrictions
Office 365 enforces strict sending limits (e.g., 30 messages per minute, 1,000 per day for most subscriptions) and requires SMTP AUTH to be enabled for your account. If you hit these limits, the server might silently drop the connection instead of returning an error. Additionally, if your account uses multi-factor authentication (MFA), you'll need an app password instead of your regular password for SMTP authentication—using your normal password will cause silent failures.
The Most Reliable Fix: Switch to MailKit
The best long-term solution is to replace System.Net.Mail with MailKit and MimeKit—these are Microsoft's recommended libraries for email in .NET, with full async support, proper timeout handling, and better compatibility with modern services like Office 365.
Here's a simplified version of your SendEmailAsync with MailKit:
using MailKit.Net.Smtp; using MimeKit; private async Task SendEmailAsync(Recipient recipient, CancellationToken cancellationToken) { var message = new MimeMessage(); message.From.Add(new MailboxAddress("Your Sender Name", "your-email@domain.com")); message.To.Add(new MailboxAddress(recipient.Name, recipient.Address)); message.Subject = "Your Email Subject"; message.Body = new TextPart("html") { Text = "Your email body content" }; using (var client = new SmtpClient()) { // Set explicit timeout client.Timeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds; // Connect to Office 365's SMTP server await client.ConnectAsync("smtp.office365.com", 587, true, cancellationToken); // Authenticate (use app password if MFA is enabled) await client.AuthenticateAsync("your-email@domain.com", "your-password-or-app-password", cancellationToken); await client.SendAsync(message, cancellationToken); await client.DisconnectAsync(true, cancellationToken); } progressViewModel.Log($"... Sent e-mail to: {recipient.Address}"); }
MailKit handles timeouts correctly, throws clear exceptions for rate limits or authentication issues, and is actively maintained—so you'll avoid the silent hangs and failures you're seeing with the old client.
内容的提问来源于stack exchange,提问作者NicolasR

