使用System.Net.Mail异步发邮件时多收件人漏发问题求助
邮件批量发送随机丢失收件人问题解决
问题描述
从TagBox选择所有收件人点击「Send Notifications」按钮时,随机出现部分收件人未收到邮件的情况;但单独选择单个收件人发送时,邮件可正常送达。
现有代码
视图代码
<p class="alert-danger">@Model</p> <form method="post"> <div id="form-demo"> <div class="widget-container"> <div class="dx-field"> <div class="dx-field-value"> @(Html.DevExtreme().TagBox() .Label("Email To:").LabelMode(EditorLabelMode.Floating) .DataSource(d => d.WebApi().Controller("LongLeadNotifications").LoadAction("LongLeadsMessagingLookup").Key("Value")) .Width(650) .ValueExpr("Value") .DisplayExpr("Text") .ShowSelectionControls(true) .ApplyValueMode(EditorApplyValueMode.UseButtons) .OnValueChanged("email_valueChanged") ) </div> </div> <div class="buttongroups-container"> @(Html.DevExtreme().ButtonGroup() .KeyExpr("alignment") .StylingMode(ButtonStylingMode.Text) .Items(items => { items.Add().Icon("email").Hint("Send Email").Text("Send Notifications"); }) .OnItemClick(@<text> function showInfo(data) { eMailToAddress.forEach(element => { console.log(element.replace("\"","").replace("[","").replace("]","").replace("\"","")); var value = element.replace("\"","").replace("[","").replace("]","").replace("\"",""); $.ajax({ type: 'POST', url: '@Url.Action("GetNotifications", "LongLeadNotifications")', data:{ strEmail: value}, dataType: 'json', success: () => { console.log(value + " is sent"); }, error: (error) => { console.log(JSON.stringify(error)); } }); }); const message = `Email is sent to ` + eMailToAddress; DevExpress.ui.notify({ message: message, position: { my: "center top", at: "center top" } }, "success", 3000); } </text>)) </div> @Html.AntiForgeryToken() </div> </div> </form>
控制器代码
public async Task<IActionResult> GetNotificationsAsync(string strEMail) { // … if (strMessageBody != "") { strMessageBody = strFirstName + "," + "<br>" + "<br>" + strIntro + strMessageBody + strClosing; await GmailSender.SendGmailAsync("myemail@cps.edu", strSubject, strMessageBody, true, null); return NoContent(); } return NoContent(); }
邮件发送类代码
using System; using System.Threading.Tasks; using System.Net.Mail; namespace CPSPMOReporting { public class GmailSender { public static async Task<string> SendGmailAsync(string ToEmails, string Subject, string Body, bool IsHtml, string AttachmentPath) { try { string GmailAccount = "myemail@cps.edu"; string GmailPassword = "mypassword"; MailMessage mail = new MailMessage(GmailAccount, ToEmails); mail.Subject = Subject; mail.IsBodyHtml = IsHtml; mail.Body = Body; if (!string.IsNullOrWhiteSpace(AttachmentPath)) { Attachment attachment = new Attachment(AttachmentPath); mail.Attachments.Add(attachment); mail.Priority = MailPriority.High; // optional } System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient("smtp.gmail.com", 587); smtp.EnableSsl = true; smtp.UseDefaultCredentials = false; smtp.Credentials = new System.Net.NetworkCredential(GmailAccount, GmailPassword); smtp.SendCompleted += (s, e) => { smtp.Dispose(); }; await smtp.SendMailAsync(mail); return ""; // return nothing when it is successful } catch (Exception ex) { return "An error occured while sending your message. " + ex.Message; } } } }
问题根源
- 前端并发请求无控制:循环中直接发起多个AJAX请求,浏览器对同一域名的并发请求数有限制(通常为6个),超出的请求会被排队或丢弃,导致部分邮件请求未被后端处理。
- 提示过早触发:循环刚启动就显示发送成功提示,实际部分请求可能还在处理或已失败,无法真实反映发送状态。
- 潜在频率限制:短时间内向Gmail发送大量邮件,可能触发其频率限制,导致部分邮件被拒绝发送。
- 路由匹配隐患:控制器方法名为
GetNotificationsAsync,但前端调用的是GetNotifications,虽ASP.NET Core会自动去除Async后缀,但仍存在路由匹配异常风险。
解决方案
前端优化:批量提交+等待请求完成
将所有收件人一次性提交到后端,或等待所有请求完成后再提示状态:
function showInfo(data) { // 正则一次性清理冗余字符 const cleanedEmails = eMailToAddress.map(element => element.replace(/["\[\]]/g, '')); // 收集所有AJAX请求Promise const sendPromises = cleanedEmails.map(email => { return $.ajax({ type: 'POST', url: '@Url.Action("GetNotificationsAsync", "LongLeadNotifications")', data: { strEMail: email }, dataType: 'json' }); }); // 等待所有请求完成,区分成功/失败 Promise.allSettled(sendPromises) .then(results => { const successEmails = []; const failedEmails = []; results.forEach((result, index) => { if (result.status === 'fulfilled') { successEmails.push(cleanedEmails[index]); console.log(cleanedEmails[index] + " is sent"); } else { failedEmails.push(cleanedEmails[index]); console.log(JSON.stringify(result.reason)); } }); // 组装提示信息 let message = ''; if (successEmails.length > 0) { message += `邮件已发送至: ${successEmails.join(', ')}`; } if (failedEmails.length > 0) { message += `\n部分邮件发送失败: ${failedEmails.join(', ')}`; } DevExpress.ui.notify({ message: message, position: { my: "center top", at: "center top" } }, successEmails.length === cleanedEmails.length ? "success" : "error", 5000); }); }
后端优化:新增批量处理接口
新增批量接收邮箱的接口,由后端统一控制发送节奏,避免前端并发问题:
[HttpPost] public async Task<IActionResult> SendBatchNotifications([FromBody] List<string> emails) { var results = new List<string>(); foreach (var email in emails) { try { if (!string.IsNullOrEmpty(strMessageBody)) { string formattedBody = strFirstName + "," + "<br><br>" + strIntro + strMessageBody + strClosing; await GmailSender.SendGmailAsync(email, strSubject, formattedBody, true, null); results.Add($"成功发送至: {email}"); } // 添加延迟避免触发Gmail频率限制 await Task.Delay(1000); } catch (Exception ex) { results.Add($"发送至{email}失败: {ex.Message}"); } } return Ok(results); }
对应前端修改为一次性提交所有收件人:
function showInfo(data) { const cleanedEmails = eMailToAddress.map(element => element.replace(/["\[\]]/g, '')); $.ajax({ type: 'POST', url: '@Url.Action("SendBatchNotifications", "LongLeadNotifications")', contentType: 'application/json', data: JSON.stringify(cleanedEmails), success: (results) => { const message = results.join('\n'); DevExpress.ui.notify({ message: message, position: { my: "center top", at: "center top" } }, results.every(r => r.startsWith("成功")) ? "success" : "error", 5000); }, error: (error) => { console.log(JSON.stringify(error)); DevExpress.ui.notify("批量发送请求失败", "error", 3000); } }); }
邮件发送类优化:资源安全释放+日志记录
public static async Task<string> SendGmailAsync(string toEmails, string subject, string body, bool isHtml, string attachmentPath) { try { string gmailAccount = "myemail@cps.edu"; string gmailPassword = "mypassword"; using (var mail = new MailMessage(gmailAccount, toEmails)) { mail.Subject = subject; mail.IsBodyHtml = isHtml; mail.Body = body; if (!string.IsNullOrWhiteSpace(attachmentPath)) { using (var attachment = new Attachment(attachmentPath)) { mail.Attachments.Add(attachment); mail.Priority = MailPriority.High; } } using (var smtp = new SmtpClient("smtp.gmail.com", 587)) { smtp.EnableSsl = true; smtp.UseDefaultCredentials = false; smtp.Credentials = new System.Net.NetworkCredential(gmailAccount, gmailPassword); await smtp.SendMailAsync(mail); } } return string.Empty; } catch (Exception ex) { // 建议添加日志记录(如注入ILogger) // _logger.LogError(ex, "发送邮件至{ToEmails}失败", toEmails); return $"发送邮件失败: {ex.Message}"; } }
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

