Contents

C#检测邮箱是否真实存在

Contents

最近在做一个小插件里边有个需求是判断邮箱地址是否真实存在,防止插件给不存在的邮箱地址发送邮件。

以往都是对邮箱地址的格式进行判断,这种意义不大,导致了很多邮件被退回而被封SMTP。后来发现可以利用TCP对邮件服务器进行发送邮件测试,原理和正常发送邮件一样,只是在最后一步提前退出流程就能达到验证目标邮箱的真实性的目的。

经过测试QQ邮箱,成功率大概在90%左右,直接上代码:

public async Task<bool> VerifyEmailAddressAsync(string emailAddress)
{
    string smtpServer = "mx1.qq.com"; 
    int port = 25;

    try
    {
        // 1. 使用 using 确保资源释放
        using (TcpClient tClient = new TcpClient())
        {
            // 异步连接,防止界面卡死,设置超时
            var connectTask = tClient.ConnectAsync(smtpServer, port);
            if (await Task.WhenAny(connectTask, Task.Delay(5000)) != connectTask)
            {
                // 连接超时
                return false; 
            }

            using (NetworkStream netStream = tClient.GetStream())
            using (StreamReader reader = new StreamReader(netStream, Encoding.ASCII))
            {
                // 读取服务器的初始欢迎信息 (例如: 220 qq.com Anti-spam GT for Coremail System)
                string response = await reader.ReadLineAsync();
                if (!CheckResponse(response, "220")) return false;

                // 2. 发送 HELO/EHLO
                await SendCommand(netStream, "HELO local.test");
                response = await reader.ReadLineAsync();
                if (!CheckResponse(response, "250")) return false;

                // 3. 发送 MAIL FROM (使用一个看起来合法的发件人)
                await SendCommand(netStream, "MAIL FROM:<check@test.com>");
                response = await reader.ReadLineAsync();
                if (!CheckResponse(response, "250")) return false;

                // 4. 发送 RCPT TO (关键步骤)
                await SendCommand(netStream, $"RCPT TO:<{emailAddress}>");
                response = await reader.ReadLineAsync();

                // 5. 判断结果
                bool isValid = false;
                if (CheckResponse(response, "250"))
                {
                    isValid = true; // 邮箱存在
                }
                else if (CheckResponse(response, "550"))
                {
                    isValid = false; // 邮箱不存在
                }
                
                // 6. 正常退出
                await SendCommand(netStream, "QUIT");
                
                return isValid;
            }
        }
    }
    catch (Exception ex)
    {
        // 处理网络错误等异常
        Console.WriteLine($"Error: {ex.Message}");
        return false;
    }
}

// 辅助方法:发送命令
private async Task SendCommand(NetworkStream stream, string command)
{
    byte[] data = Encoding.ASCII.GetBytes(command + "\r\n");
    await stream.WriteAsync(data, 0, data.Length);
}

// 辅助方法:检查响应代码
private bool CheckResponse(string response, string expectedCode)
{
    if (string.IsNullOrEmpty(response)) return false;
    // SMTP 响应格式通常是 "CODE Description",例如 "250 OK"
    return response.StartsWith(expectedCode);
}

这里代码重要的是在发送RCPT TO后一定要等待,最好是循环判断是否返回250 ok 或者550