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

使用SMTP协议通过IPv6地址发送邮件失败的解决办法

问题概述

使用smtp.mail.ru作为SMTP服务器时,该域名解析出多个IP地址(包含IPv6地址),手动遍历这些IP发送邮件时,遇到IPv6地址会抛出连接异常:

org.springframework.mail.MailSendException: Mail server connection failed; nested exception is com.sun.mail.util.MailConnectException: Couldn't connect to host, port: 2a00:1148:db00:0:0:0:0:8, 465; timeout -1;
  nested exception is:
    java.net.SocketException: Network unreachable. Failed messages: com.sun.mail.util.MailConnectException: Couldn't connect to host, port: 2a00:1148:db00:0:0:0:0:8, 465; timeout -1;
  nested exception is:
    java.net.SocketException: Network unreachable

当前获取IP的代码:

InetAddress[] addresses = InetAddress.getAllByName("smtp.mail.ru");
log.warn("Read addresses {}", Arrays.asList(addresses));

执行结果显示包含IPv4和IPv6地址:

2023-06-22 16:37:09.216  WARN 1 --- [           main] r.v.u.e.s.MyClasss            : Read addresses [smtp.mail.ru/94.100.180.160, smtp.mail.ru/217.69.139.160, smtp.mail.ru/2a00:1148:db00:0:0:0:0:8]

邮件发送逻辑为手动切换JavaMailSenderImpl的host为遍历到的IP地址,再执行发送操作。


解决办法

1. 过滤IPv6,仅使用IPv4地址

既然IPv6连接失败,直接在获取IP阶段筛选出仅IPv4的地址,避免无效尝试。修改getNextAddress方法:

private String getNextAddress(){
    InetAddress[] all = InetAddress.getAllByName(hostName);
    // 筛选所有IPv4地址
    List<InetAddress> ipv4List = Arrays.stream(all)
            .filter(addr -> addr instanceof Inet4Address)
            .collect(Collectors.toList());
    if (ipv4List.isEmpty()) {
        throw new IllegalStateException(hostName + " 未找到可用IPv4地址");
    }
    // 循环获取下一个地址,防止索引越界
    index = (index + 1) % ipv4List.size();
    return ipv4List.get(index).getHostAddress();
}

2. 连接失败自动重试下一个IP

不想过滤IPv6的话,添加重试逻辑:连接当前IP失败时,自动切换到下一个地址重试,直到发送成功或所有IP尝试完毕。调整发送代码:

public void foo() {
    int tryTimes = 0;
    InetAddress[] allAddrs = InetAddress.getAllByName(hostName);
    boolean sendSuccess = false;

    while (!sendSuccess && tryTimes < allAddrs.length) {
        try {
            sender.setHost(getNextAddress());
            sender.send(message);
            sendSuccess = true;
        } catch (MailSendException e) {
            tryTimes++;
            log.error("通过{}发送邮件失败,进行第{}次重试", sender.getHost(), tryTimes, e);
            // 所有IP尝试完毕仍失败,抛出异常
            if (tryTimes >= allAddrs.length) {
                throw e;
            }
        }
    }
}

对应的getNextAddress需要保证循环取地址:

private int index = 0;
private String getNextAddress(){
    InetAddress[] all = InetAddress.getAllByName(hostName);
    index = (index + 1) % all.length;
    return all[index].getHostAddress();
}

3. 全局设置JVM优先使用IPv4

如果整个应用环境都无法使用IPv6,可让JVM默认优先使用IPv4。两种方式:

  • 在JVM启动参数中添加:
    -Djava.net.preferIPv4Stack=true
    
  • 在代码的网络操作前设置系统属性:
    System.setProperty("java.net.preferIPv4Stack", "true");
    

设置后InetAddress.getAllByName会优先返回IPv4地址,后续网络操作默认走IPv4。

4. 直接使用域名(最省心)

JavaMailSenderImpl本身支持直接用域名作为host,JavaMail底层会自动处理域名解析后的IP遍历和重试逻辑,无需手动切换IP。修改代码如下:

@Autowired
private JavaMailSenderImpl sender;

// 初始化邮件发送配置
@PostConstruct
public void initMailSender() {
    sender.setHost("smtp.mail.ru");
    sender.setPort(465); // smtp.mail.ru的SSL端口为465
    sender.setUsername("你的邮箱@mail.ru");
    sender.setPassword("你的邮箱密码");
    // 启用SSL加密
    Properties props = sender.getJavaMailProperties();
    props.put("mail.smtp.ssl.enable", "true");
}

public void foo() {
    sender.send(message);
}

这种方式下,若某个IP连接失败,底层会自动切换至下一个IP重试,无需手动实现遍历逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 08:03:23