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

Gmail邮箱SMTP验证异常问题求助及代码排查

问题描述

我开发了一个API,接收名字、姓氏和域名,生成所有可能的邮箱组合并返回真实存在的有效邮箱。该API对innofied.com域名验证正常,但对gmail.com域名的SMTP验证失效,会将大量不存在的邮箱判定为有效,当前使用Gmail SMTP进行验证。

相关代码

api.php 路由

Route::post('/validate-emails', [EmailValidationController::class, 'validateEmails']);

EmailValidationController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Services\Integrations\Email\EmailServiceInterface;

class EmailValidationController extends Controller
{
    public function __construct(
        private EmailServiceInterface $emailValidationService
    ) {}

    public function validateEmails(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'first_name' => 'required|string',
            'last_name' => 'required|string',
            'domain' => 'required|string'
        ]);

        if ($validator->fails()) {
            return response()->json(['error' => $validator->errors()], 422);
        }

        $first = strtolower($request->first_name);
        $last = strtolower($request->last_name);
        $domain = strtolower($request->domain);

        $emails = $this->emailValidationService->generateEmailPermutations($first, $last, $domain);
        $validEmails = $this->emailValidationService->validateEmails($emails);

        return response()->json([
            'all_combinations' => $emails,
            'valid_combinations' => array_values($validEmails),
        ]);
    }
}

EmailService.php

<?php

namespace App\Services\Integrations\Email;

use App\Services\Integrations\Email\EmailServiceInterface;
use Illuminate\Support\Facades\Log;

class EmailService implements EmailServiceInterface
{
    /**
     * Generate all possible email permutations.
     *
     * @param string $firstName
     * @param string $lastName
     * @param string $domain
     * @return array
     */
    public function generateEmailPermutations(
        string $firstName,
        string $lastName,
        string $domain
    ): array {
        $first = strtolower($firstName);
        $last = strtolower($lastName);
        $f = substr($first, 0, 1);
        $l = substr($last, 0, 1);

        return [
            "$first@$domain",
            "$last@$domain",
            "$first$last@$domain",
            "$first.$last@$domain",
            "$f$last@$domain",
            "$f.$last@$domain",
            "$first$l@$domain",
            "$first.$l@$domain",
            "$f$l@$domain",
            "$f.$l@$domain",
            "$last$first@$domain",
            "$last.$first@$domain",
            "$last$f@$domain",
            "$last.$f@$domain",
            "$l$first@$domain",
            "$l.$first@$domain",
            "$l$f@$domain",
            "$l.$f@$domain",
            "$first-$last@$domain",
            "$f-$last@$domain",
            "$first-$l@$domain",
            "$f-$l@$domain",
            "$last-$first@$domain",
            "$last-$f@$domain",
            "$l-$first@$domain",
            "$l-$f@$domain",
            "{$first}_{$last}@$domain",
            "{$f}_{$last}@$domain",
            "{$first}_{$l}@$domain",
            "{$f}_{$l}@$domain",
            "{$last}_{$first}@$domain",
            "{$last}_{$f}@$domain",
            "{$l}_{$first}@$domain",
            "{$l}_{$f}@$domain",
        ];
    }

    /**
     * Validate email addresses using MX and SMTP verification.
     */
    public function validateEmails(array $emails): array
    {
        return array_filter($emails, fn($email) => $this->validateEmailSMTP($email));
    }

    private function getMXRecord($domain)
    {
        $mxRecords = dns_get_record($domain, DNS_MX);
        return $mxRecords ? $mxRecords[0]['target'] : null;
    }

    private function validateEmailSMTP($email)
    {
        $domain = explode('@', $email)[1];
        $mxServer = $this->getMXRecord($domain);

        if (!$mxServer) {
            return false; // No mail server found
        }

        $from = config('mail.from.address');
        $mailServerDomain = config('mail.mailers.smtp.mail_server_domain');
        $sock = fsockopen($mxServer, 25, $errno, $errstr, 5);

        if (!$sock) {
            return false;
        }

        stream_set_timeout($sock, 5);

        $this->getSMTPResponse($sock); // Read initial 220 response
        fwrite($sock, "HELO $mailServerDomain\r\n");
        $this->getSMTPResponse($sock); // Read HELO response

        fwrite($sock, "MAIL FROM:<$from>\r\n");
        $this->getSMTPResponse($sock); // Read MAIL FROM response

        fwrite($sock, "RCPT TO:<$email>\r\n");
        $response = $this->getSMTPResponse($sock); // Read RCPT TO response

        fwrite($sock, "QUIT\r\n");
        fclose($sock);

        return strpos($response, "250") !== false && !strpos($response, "550");
    }

    private function getSMTPResponse($sock)
    {
        $response = '';
        while ($line = fgets($sock, 1024)) {
            $response .= $line;
            if (substr($line, 3, 1) == " ") { // End of response
                break;
            }
        }
        return $response;
    }
}

验证结果对比

自定义域名(innofied.com)验证正常

输入

{
    "first_name": "deep",
    "last_name": "jajodia",
    "domain": "innofied.com"
}

输出

{
    "all_combinations": [
        "deep@innofied.com",
        "jajodia@innofied.com",
        "deepjajodia@innofied.com",
        "deep.jajodia@innofied.com",
        "djajodia@innofied.com",
        "d.jajodia@innofied.com",
        "deepj@innofied.com",
        "deep.j@innofied.com",
        "dj@innofied.com",
        "d.j@innofied.com",
        "jajodiadeep@innofied.com",
        "jajodia.deep@innofied.com",
        "jajodiad@innofied.com",
        "jajodia.d@innofied.com",
        "jdeep@innofied.com",
        "j.deep@innofied.com",
        "jd@innofied.com",
        "j.d@innofied.com",
        "deep-jajodia@innofied.com",
        "d-jajodia@innofied.com",
        "deep-j@innofied.com",
        "d-j@innofied.com",
        "jajodia-deep@innofied.com",
        "jajodia-d@innofied.com",
        "j-deep@innofied.com",
        "j-d@innofied.com",
        "deep_jajodia@innofied.com",
        "d_jajodia@innofied.com",
        "deep_j@innofied.com",
        "d_j@innofied.com",
        "jajodia_deep@innofied.com",
        "jajodia_d@innofied.com",
        "j_deep@innofied.com",
        "j_d@innofied.com"
    ],
    "valid_combinations": [
        "deep@innofied.com",
        "deep.jajodia@innofied.com"
    ]
}

实际deep@innofied.com和deep.jajodia@innofied.com为有效邮箱,验证结果正确。

Gmail域名验证失效

输入

{
    "first_name": "sourav",
    "last_name": "saha964",
    "domain": "gmail.com"
}

输出

{
    "all_combinations": [
        "sourav@gmail.com",
        "saha964@gmail.com",
        "souravsaha964@gmail.com",
        "sourav.saha964@gmail.com",
        "ssaha964@gmail.com",
        "s.saha964@gmail.com",
        "souravs@gmail.com",
        "sourav.s@gmail.com",
        "ss@gmail.com",
        "s.s@gmail.com",
        "saha964sourav@gmail.com",
        "saha964.sourav@gmail.com",
        "saha964s@gmail.com",
        "saha964.s@gmail.com",
        "ssourav@gmail.com",
        "s.sourav@gmail.com",
        "ss@gmail.com",
        "s.s@gmail.com",
        "sourav-saha964@gmail.com",
        "s-saha964@gmail.com",
        "sourav-s@gmail.com",
        "s-s@gmail.com",
        "saha964-sourav@gmail.com",
        "saha964-s@gmail.com",
        "s-sourav@gmail.com",
        "s-s@gmail.com",
        "sourav_saha964@gmail.com",
        "s_saha964@gmail.com",
        "sourav_s@gmail.com",
        "s_s@gmail.com",
        "saha964_sourav@gmail.com",
        "saha964_s@gmail.com",
        "s_sourav@gmail.com",
        "s_s@gmail.com"
    ],
    "valid_combinations": [
        "sourav@gmail.com",
        "saha964@gmail.com",
        "souravsaha964@gmail.com",
        "sourav.saha964@gmail.com",
        "ssaha964@gmail.com",
        "s.saha964@gmail.com",
        "souravs@gmail.com",
        "sourav.s@gmail.com",
        "ssourav@gmail.com",
        "s.sourav@gmail.com"
    ]
}

实际仅souravsaha964@gmail.com为有效邮箱,其余均不存在,但API误判为有效。

问题原因及解决方案

原因分析

Gmail的SMTP服务器出于隐私和反垃圾邮件策略,不会直接返回550(邮箱不存在)的响应,对所有RCPT TO请求统一返回250状态码。此外,Gmail会忽略本地部分的点号、加号后缀,例如sourav.saha964@gmail.com和souravsaha964@gmail.com会被视为同一个邮箱,这也导致现有验证逻辑误判。

解决方案

  1. Gmail邮箱标准化处理
    先对Gmail邮箱做标准化处理,再去重验证:

    private function validateEmailSMTP($email)
    {
        list($local, $domain) = explode('@', $email);
        
        // 标准化Gmail邮箱
        if (in_array($domain, ['gmail.com', 'googlemail.com'])) {
            // 移除本地部分的点号
            $local = str_replace('.', '', $local);
            // 移除加号及后续内容
            $plusPos = strpos($local, '+');
            if ($plusPos !== false) {
                $local = substr($local, 0, $plusPos);
            }
            $email = "$local@$domain";
        }
        
        $mxServer = $this->getMXRecord($domain);
        
        // 原有SMTP验证逻辑...
    }
    

    同时在生成邮箱组合后,对Gmail的组合提前去重,避免重复验证同一实际邮箱。

  2. 优化SMTP验证流程
    增加DATA命令测试,部分服务器会在DATA阶段暴露真实邮箱状态:

    fwrite($sock, "RCPT TO:<$email>\r\n");
    $response = $this->getSMTPResponse($sock);
    
    $isValid = false;
    if (strpos($response, "250") !== false) {
        fwrite($sock, "DATA\r\n");
        $dataResponse = $this->getSMTPResponse($sock);
        if (strpos($dataResponse, "354") !== false) {
            fwrite($sock, ".\r\n");
            $finalResponse = $this->getSMTPResponse($sock);
            // 最终响应不含550则视为有效
            $isValid = strpos($finalResponse, "550") === false;
        }
    }
    
    fwrite($sock, "QUIT\r\n");
    fclose($sock);
    
    return $isValid;
    

    注意:该操作可能被部分服务器判定为垃圾邮件尝试,存在IP被封禁的风险。

  3. 替换验证方式
    对于Gmail这类大型服务商,SMTP验证可靠性低,可改用专业邮箱验证服务,或结合其他辅助验证手段(如社交账号关联检测,但准确性有限)。


内容的提问来源于stack exchange,提问作者Sourav Saha

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 10:57:01