SMTP发信报错:Could not connect to SMTP host

问题描述

连接SMTP服务器发送邮件,出现错误提示,如“Could not connect to SMTP host”。

问题原因

出现这种报错,是因为程序在发送邮件的最开始阶段,连接邮箱服务器时就出现了问题,此时邮箱服务端没有入信记录,不会有相关发信日志,通常需要从SMTP发信代码所在客户端开始排查。

解决方案

按步骤排查分析,根据具体情况采取对应措施。

1、事件分析

首先判断之前是好的吗?是否每次调用都报错(错误是偶现还是必现)?

如果之前是好的,那么出问题前是否做了代码更改,组件升级等操作,尝试回滚恢复。

若错误能复现会便于后续问题排查。

2、连通性分析

以465端口为例,请运行下面命令,并截图。

命令1(查看分配到的服务器IP,不同区域IP会不同):

ping smtp.qiye.aliyun.com

ping命令失败或有高延迟,可能是本地禁止ping或网络连接问题。

成功结果

image

命令2(通过指定端口连接SMTP服务器,查看端口和网络连通性):

telnet smtp.qiye.aliyun.com 465

成功结果:

image

image

命令3(检查路由路径):

#linux或Mac命令:
mtr -n -i 1 -c 100 smtp.qiye.aliyun.com
traceroute smtp.qiye.aliyun.com
#windows命令:
tracert smtp.qiye.aliyun.com
pathping smtp.qiye.aliyun.com

查看到SMTP服务器的路由路径,并识别哪一跳开始出现问题。

tracert默认最多显示 30 跳,最后一个IP是邮箱服务器IP就行。

pathping运行较慢需要几分钟时间生成统计信息。

也可以使用WinMTR等可视化开源工具。

成功结果

image

命令4(仅针对465端口):

#不指定TLS版本
openssl s_client -connect smtp.qiye.aliyun.com:465
#指定TLS版本
openssl s_client -connect smtp.qiye.aliyun.com:465 -tls1_2

使用加密方式进行连接测试,判断是否是TLS版本导致的握手异常。

成功结果:

CONNECTED

image

3、排除分析

  • 排查其他端口是否可以正常发信,如25,80端口(需要注意ECS默认禁止25端口),可以尝试80端口发信是否成功,这两个端口不用SSL相关代码,请注释掉相关代码。

    • 若其他端口正常,465不行:465端口强制启用SSL

      final Properties props = new Properties();
      props.setProperty("mail.smtp.ssl.enable", "true"); 
  • 尝试从其他脚本或使用示例代码独立发信(排除其他业务逻辑的代码干扰)。

    阿里邮箱产品示例:

    阿里邮箱SMTP发信示例

    邮件推送产品示例(仅参考):

    SMTP 之 Java 调用示例

  • 使用其他服务器地址用465端口发信测试:如腾讯邮箱smtp.qq.com,网易邮箱smtp.163.com,阿里云邮件推送smtpdm.aliyun.com等。

4、抓包分析

若25或80端口没有禁用(465端口加密,看不到细节),可以对25或80端口抓包分析。

抓包过程及命令参考:

  • 发信端启动抓包(指定邮箱IP和端口)

    • linux命令参考:默认保存根目录,也可以修改命令指定保存目录,如/tmp/capture.pcap

      sudo tcpdump -i eth0 host xx.xx.xx.xx and port 80 -w capture.pcap
    • windows:使用wireshark。

  • 测试发信并复现问题

  • 停止抓包

    • linux:在抓包命令执行界面按ctrl+c停止抓包。

    • windows:软件上操作停止按钮。

  • 用wireshark打开分析。

其他可能方案

  • 握手阶段加密算法不匹配

    Java环境为例,JRE 高版本把加密算法禁止了 SSLv3, TLSv1, TLSv1.1等,尝试指定版本或删除相关禁用项。

    final Properties props = new Properties();
    props.put("mail.smtp.ssl.protocols", "TLSv1.2"); 
  • SSL来建立连接,失败则回退兼容

    如果出于某种原因无法使用SSL来建立连接(比如服务器不支持),则不会回退到非加密的连接方式,而是让连接尝试失败。

    final Properties props = new Properties();
    prop.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
    prop.put("mail.smtp.socketFactory.fallback", "false");

其他分析方式

开启debug模式,(会打印与服务器的交互过程,可以用于其他阶段报错分析,当前场景可能不适用)。

Java示例:

//开启debug模式:
Session mailSession = Session.getInstance(props, authenticator);
mailSession.setDebug(true);