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会被视为同一个邮箱,这也导致现有验证逻辑误判。
解决方案
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的组合提前去重,避免重复验证同一实际邮箱。
优化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被封禁的风险。
替换验证方式
对于Gmail这类大型服务商,SMTP验证可靠性低,可改用专业邮箱验证服务,或结合其他辅助验证手段(如社交账号关联检测,但准确性有限)。
内容的提问来源于stack exchange,提问作者Sourav Saha
相关产品推荐
相关产品推荐

