You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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;
            }
        }
    }
}

问题根源

  1. 前端并发请求无控制:循环中直接发起多个AJAX请求,浏览器对同一域名的并发请求数有限制(通常为6个),超出的请求会被排队或丢弃,导致部分邮件请求未被后端处理。
  2. 提示过早触发:循环刚启动就显示发送成功提示,实际部分请求可能还在处理或已失败,无法真实反映发送状态。
  3. 潜在频率限制:短时间内向Gmail发送大量邮件,可能触发其频率限制,导致部分邮件被拒绝发送。
  4. 路由匹配隐患:控制器方法名为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 07:55:21