临时邮箱概述

临时邮箱(Disposable Email Address, DEA)是一种短期有效的邮箱地址,用户无需注册即可使用,通常存活时间从几分钟到几小时不等。常见的临时邮箱服务商包括10minutemail.com、guerrillamail.com、mailinator.com、tempmail.com、throwaway.email、yopmail.com、trashmail.com等。虽然临时邮箱为用户提供了隐私保护,但在业务系统中被大量用于恶意注册、刷单、垃圾评论、薅羊毛等行为,因此检测并过滤临时邮箱是风控系统的重要组成部分。

临时邮箱服务商生态分析

临时邮箱服务商按照功能和特点可以分为以下几类:

类别特点代表服务检测难度
主流临时邮箱知名度高,用户量大,通常有固定域名10minutemail.com, mailinator.com, guerrillamail.com低
一次性邮箱用完即弃,域名随机生成,自动销毁temp-mail.org, getnada.com, emailnator.com中
匿名邮箱不要求注册信息,注重隐私保护protonmail.com(免费版), tutanota.com高
隐私邮箱提供邮件转发和别名服务,隐藏真实邮箱simplelogin.io, anonaddy.com, firefox relay高

近年来,临时邮箱服务商呈现出以下趋势:域名轮换频率加快,越来越多的服务商使用随机生成的一级域名;部分服务商开始提供付费长期服务,模糊了临时邮箱与普通邮箱的界限;一些服务商开始提供API接口,方便开发者集成。

邮箱格式验证(RFC 5321/5322标准)

在进行深度检测之前,首先需要验证邮箱地址的格式是否符合标准。RFC 5321(SMTP协议)和RFC 5322(互联网消息格式)定义了邮箱地址的语法规范:

function validateEmailFormat(string $email): array
{
    // RFC 5321: 邮箱地址最大长度254字符
    if (strlen($email) > 254) {
        return ['valid' => false, 'reason' => 'Email too long (>254 chars)'];
    }

    // 检查是否包含@符号
    $atPos = strrpos($email, '@');
    if ($atPos === false) {
        return ['valid' => false, 'reason' => 'Missing @ symbol'];
    }

    $localPart = substr($email, 0, $atPos);
    $domain = substr($email, $atPos + 1);

    // 本地部分长度1-64字符
    if (strlen($localPart) < 1 || strlen($localPart) > 64) {
        return ['valid' => false, 'reason' => 'Invalid local part length'];
    }

    // 域名部分长度1-255字符,且至少包含一个点
    if (strlen($domain) < 1 || strlen($domain) > 255) {
        return ['valid' => false, 'reason' => 'Invalid domain length'];
    }

    // 本地部分不允许以点开头或结尾
    if ($localPart[0] === '.' || $localPart[-1] === '.') {
        return ['valid' => false, 'reason' => 'Local part starts/ends with dot'];
    }

    // 不允许连续两个点
    if (strpos($localPart, '..') !== false) {
        return ['valid' => false, 'reason' => 'Consecutive dots in local part'];
    }

    // RFC 5321 允许的本地部分字符
    if (!preg_match('/^[a-zA-Z0-9.!#$%&\'*+\\/=?^_`{|}~-]+$/', $localPart)) {
        return ['valid' => false, 'reason' => 'Invalid characters in local part'];
    }

    // 域名格式验证
    if (!preg_match('/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/', $domain)) {
        return ['valid' => false, 'reason' => 'Invalid domain format'];
    }

    return ['valid' => true, 'local_part' => $localPart, 'domain' => $domain];
}

邮箱域名DNS深度检测

DNS深度检测通过分析邮箱域名的DNS记录来判断邮箱的可信度,是临时邮箱检测的重要手段:

MX记录检测

检查域名是否存在MX记录,以及MX记录指向的邮件服务器是否属于已知的临时邮箱服务商。临时邮箱的MX记录通常指向特定的邮件服务器,而不是主流的邮件服务商。如果没有MX记录,则说明该域名无法接收邮件,高度可疑。

SPF记录检测

SPF(Sender Policy Framework)记录指定了允许发送邮件的服务器IP地址。临时邮箱服务商的SPF记录通常包含大量IP段,或者指向特定的邮件发送服务。SPF记录格式为v=spf1开头,后跟允许的IP地址或域名。

DKIM签名检测

DKIM(DomainKeys Identified Mail)通过数字签名验证邮件的完整性和来源。临时邮箱服务商通常不会配置DKIM签名,或者签名信息不完整。检查域名的DKIM记录(格式为default._domainkey.example.com的TXT记录)可以辅助判断。

DMARC策略检测

DMARC(Domain-based Message Authentication, Reporting & Conformance)策略定义了邮件验证失败后的处理方式。临时邮箱域名的DMARC策略通常为p=none(不处理),而正规邮箱服务商通常会设置p=quarantine或p=reject。

class DnsDeepCheck
{
    public function checkDnsRecords(string $domain): array
    {
        $results = [];

        // MX记录检查
        $mxHosts = [];
        if (getmxrr($domain, $mxHosts)) {
            $results['mx'] = ['exists' => true, 'hosts' => $mxHosts];
            $suspiciousMx = ['mailinator', 'guerrillamail', '10minutemail', 'temp-mail', 'trashmail'];
            foreach ($mxHosts as $host) {
                foreach ($suspiciousMx as $keyword) {
                    if (stripos($host, $keyword) !== false) {
                        $results['mx']['suspicious'] = true;
                        break 2;
                    }
                }
            }
        } else {
            $results['mx'] = ['exists' => false, 'suspicious' => true];
        }

        // SPF记录检查
        $spfRecords = dns_get_record($domain, DNS_TXT);
        $results['spf'] = ['exists' => false];
        foreach ($spfRecords as $record) {
            if (strpos($record['txt'], 'v=spf1') === 0) {
                $results['spf'] = ['exists' => true, 'value' => $record['txt']];
                break;
            }
        }

        // DMARC记录检查
        $dmarcRecords = dns_get_record('_dmarc.' . $domain, DNS_TXT);
        $results['dmarc'] = ['exists' => false];
        foreach ($dmarcRecords as $record) {
            if (strpos($record['txt'], 'v=DMARC1') === 0) {
                $results['dmarc'] = ['exists' => true, 'value' => $record['txt']];
                break;
            }
        }

        return $results;
    }
}

SMTP会话完整握手流程

SMTP验证是最精确的检测方法之一,通过模拟SMTP邮件发送过程来验证邮箱地址是否存在且可用。其核心原理是不真正发送邮件,而是在SMTP协议交互的RCPT TO阶段验证邮箱地址的有效性。完整的SMTP握手流程如下:

// SMTP握手完整流程
C: <连接到SMTP服务器端口25>
S: 220 mx.example.com ESMTP Postfix (Welcome message)
C: EHLO verifier.example.com
S: 250-mx.example.com
S: 250-PIPELINING
S: 250-SIZE 10240000
S: 250-VRFY
S: 250-ETRN
S: 250-STARTTLS
S: 250-AUTH LOGIN PLAIN
S: 250-ENHANCEDSTATUSCODES
S: 250-8BITMIME
S: 250 DSN
C: MAIL FROM:<verify@example.com>
S: 250 2.1.0 Ok
C: RCPT TO:<target@example.com>
S: 250 2.1.5 Ok  <-- 邮箱存在
-- 或 --
S: 550 5.1.1 User unknown  <-- 邮箱不存在
C: QUIT
S: 221 2.0.0 Bye
class SmtpVerifier
{
    private int $timeout = 10;
    private string $fromEmail = 'check@verify-domain.com';

    public function verify(string $email): array
    {
        $domain = substr(strrchr($email, '@'), 1);
        $mxHosts = [];
        if (!getmxrr($domain, $mxHosts)) {
            return ['valid' => false, 'score' => 0, 'detail' => 'No MX records'];
        }

        $conn = @fsockopen($mxHosts[0], 25, $errno, $errstr, $this->timeout);
        if (!$conn) {
            return ['valid' => false, 'score' => 0, 'detail' => "Connection failed: $errstr"];
        }

        stream_set_timeout($conn, $this->timeout);
        $response = '';

        // 读取欢迎信息
        $response .= fgets($conn, 512);

        // EHLO
        fwrite($conn, "EHLO verifier.local\r\n");
        while ($line = fgets($conn, 512)) {
            $response .= $line;
            if (substr($line, 3, 1) === ' ') break;
        }

        // MAIL FROM
        fwrite($conn, "MAIL FROM:<{$this->fromEmail}>\r\n");
        $response .= fgets($conn, 512);

        // RCPT TO - 关键检查点
        fwrite($conn, "RCPT TO:<{$email}>\r\n");
        $rcptResponse = fgets($conn, 512);
        $response .= $rcptResponse;

        // QUIT
        fwrite($conn, "QUIT\r\n");
        fgets($conn, 512);
        fclose($conn);

        $code = (int)substr($rcptResponse, 0, 3);
        if ($code === 250) {
            return ['valid' => true, 'score' => 100, 'detail' => 'SMTP verification passed'];
        } elseif (in_array($code, [550, 551, 552, 553, 450, 451])) {
            return ['valid' => false, 'score' => 0, 'detail' => "SMTP refused: code $code"];
        }
        return ['valid' => null, 'score' => 50, 'detail' => "SMTP response: $code"];
    }
}

SMTP验证的局限性

SMTP验证虽然精度高,但在实际应用中存在以下局限性:

  • 服务器反探测:部分邮件服务器配置了反垃圾邮件策略,会检测并拒绝未经认证的SMTP连接,返回虚假的"用户存在"或"用户不存在"信息
  • 灰名单(Greylisting):部分服务器使用灰名单技术,对首次连接临时拒绝(返回450状态码),要求客户端重试。这会导致SMTP验证需要多次尝试
  • 速率限制:频繁的SMTP验证请求可能导致IP被临时封禁,需要控制验证频率并合理使用多个验证节点
  • 连接超时:部分邮件服务器响应缓慢,SMTP验证可能耗时3-5秒,影响用户体验
  • 端口封锁:部分云服务商和ISP封锁了25端口,导致无法直接建立SMTP连接

域名黑名单维护策略

黑名单的维护是临时邮箱检测系统的核心工作,需要持续更新以保证检测效果。系统采用以下策略维护黑名单:

自动发现机制

定期爬取临时邮箱服务商列表网站(如Wikipedia的Comparison of disposable email services页面、GitHub上社区维护的disposable-email-domains项目),自动发现新的临时邮箱域名。同时,通过分析用户注册数据中的异常模式,发现新出现的临时邮箱域名。

社区共享

参与开源黑名单项目,如@mailsac/email-list、disposable-email-domains、mailcheck等,与其他安全团队共享威胁情报。这种社区协作方式能够快速获取新发现的临时邮箱域名。

机器学习辅助

使用机器学习模型分析域名的特征(注册时间、注册商、MX记录模式、域名年龄、Alexa排名等),自动识别疑似临时邮箱域名。模型可以识别出尚未被收录到黑名单中的新域名,实现零日检测。

class BlacklistManager
{
    private const CACHE_KEY = 'temp_email:blacklist';
    private const CACHE_TTL = 86400;
    private Redis $redis;

    public function isBlacklisted(string $email): bool
    {
        $domain = strtolower(substr(strrchr($email, '@'), 1));
        $blacklist = $this->getBlacklist();
        return in_array($domain, $blacklist, true);
    }

    public function getBlacklist(): array
    {
        $cached = $this->redis->get(self::CACHE_KEY);
        if ($cached !== false) {
            return json_decode($cached, true);
        }
        // 从本地文件加载
        $file = __DIR__ . '/temp_email_domains.json';
        $list = file_exists($file) ? json_decode(file_get_contents($file), true) : [];
        $this->redis->setex(self::CACHE_KEY, self::CACHE_TTL, json_encode($list));
        return $list;
    }

    public function addDomain(string $domain): void
    {
        $domain = strtolower(trim($domain));
        $list = $this->getBlacklist();
        if (!in_array($domain, $list, true)) {
            $list[] = $domain;
            $this->saveBlacklist($list);
        }
    }

    private function saveBlacklist(array $list): void
    {
        $file = __DIR__ . '/temp_email_domains.json';
        file_put_contents($file, json_encode($list, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
        $this->redis->setex(self::CACHE_KEY, self::CACHE_TTL, json_encode($list));
    }
}

检测结果可信度评分模型

系统采用综合评分模型,将多种检测方法的结果加权融合,生成最终的可信度评分:

检测维度检测方法权重最高分
域名黑名单域名是否在黑名单中25%100
DNS探测MX记录、域名年龄、SPF/DKIM/DMARC20%80
SMTP验证邮箱地址是否存在25%100
启发式规则域名模式、TLD、邮箱名模式15%60
域名信誉注册时间、注册商、IP信誉15%60
class EmailScoreModel
{
    private array $weights = [
        'blacklist' => 0.25,
        'dns' => 0.20,
        'smtp' => 0.25,
        'heuristic' => 0.15,
        'reputation' => 0.15,
    ];

    public function evaluate(string $email, array $checkResults): array
    {
        $totalScore = 0;
        foreach ($this->weights as $dimension => $weight) {
            $score = $checkResults[$dimension]['score'] ?? 0;
            $totalScore += $score * $weight;
        }
        $totalScore = round($totalScore, 2);
        return [
            'is_disposable' => $totalScore >= 50,
            'total_score' => $totalScore,
            'level' => $totalScore >= 80 ? 'highly_likely' : ($totalScore >= 50 ? 'likely' : ($totalScore >= 30 ? 'suspicious' : 'clean')),
            'details' => $checkResults,
        ];
    }
}

批量检测性能优化

在实际业务场景中,经常需要批量检测大量邮箱地址。性能优化方案包括:

  • 连接池:对SMTP连接使用连接池技术,复用TCP连接,减少连接建立的开销
  • 异步检测:使用消息队列(如Redis队列或RabbitMQ)将检测请求异步处理,前端立即返回受理状态,后台消费者完成检测后回调通知
  • 缓存域名级别结果:同一域名下的多个邮箱地址共享DNS探测和黑名单检查结果,避免重复查询。缓存键为域名,缓存时间为1小时
  • 并发控制:使用Golang协程或PHP的Swoole/Fiber实现并发SMTP验证,将单次检测的等待时间从串行的3-5秒降低到并行的1秒以内
class BatchEmailVerifier
{
    private EmailVerifier $verifier;
    private array $domainCache = [];

    public function batchVerify(array $emails): array
    {
        $results = [];
        $batch = [];

        // 先按域名分组,共享域名级检测结果
        foreach ($emails as $email) {
            $domain = substr(strrchr($email, '@'), 1);
            $batch[$domain][] = $email;
        }

        foreach ($batch as $domain => $domainEmails) {
            // 域名级检测只做一次
            if (!isset($this->domainCache[$domain])) {
                $this->domainCache[$domain] = $this->verifier->checkDomain($domain);
            }
            $domainResult = $this->domainCache[$domain];

            foreach ($domainEmails as $email) {
                $results[$email] = $this->verifier->verifyWithCache($email, $domainResult);
            }
        }

        return $results;
    }
}

API接口设计

接口路径参数返回数据
单邮箱检测GET /api/email/verifyemailis_disposable, score, level, details
批量检测POST /api/email/batch-verifyemails[]批量检测结果数组
域名检查GET /api/email/domain-checkdomainDNS记录、黑名单状态、信誉评分