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

使用Socket类基于SMTP协议发送邮件的Java代码故障排查

问题:手动实现SMTP邮件发送时持续收到250响应码导致发送失败

我正在开发一个基于SMTP协议发送邮件的Java类,不使用任何简化邮件发送的库(如MimeMessage或MailUtility)。以下是目前完成的代码:

public class MailSender {

    protected String localMachine;
    protected String localSMTPServer;
    private String textMail;

    private String mailFrom;

    public MailSender(String localSMTPServer, String mailFrom) {
        try {
            this.textMail = "null";
            this.localSMTPServer = localSMTPServer;
            this.mailFrom = mailFrom;

            InetAddress localIP = InetAddress.getLocalHost();
            this.localMachine = localIP.getHostAddress();

        } catch (UnknownHostException ex) {
            Logger.getLogger(MailSender.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    //getter and setter
    public void smtp(String command) {
        try {
            System.out.println(sIn.readLine());
            os.write((command + "\n").getBytes());
            System.out.println("end");
        } catch (IOException ex) {
            System.out.println(ex);
            Logger.getLogger(MailSender.class.getName()).log(Level.SEVERE, null, ex);
        }           
    }

    BufferedReader sIn;
    OutputStream os;

    public Boolean trySend(String destUser, String subject, String message) {
        Socket socket;
        try {
            socket = new Socket(localSMTPServer, 587);

            OutputStreamWriter sOutW = new OutputStreamWriter(socket.getOutputStream(), "ASCII");

            PrintWriter sOut = new PrintWriter(sOutW, true);
            InputStreamReader sInR = new InputStreamReader(socket.getInputStream(), "ASCII");
            
            sIn = new BufferedReader(sInR);
            os = socket.getOutputStream();
            this.nextSMTPCode(sIn, 220);
            sOut.println("EHLO");
            this.nextSMTPCode(sIn, 250);
          
            sOut.println("AUTH LOGIN");
            this.nextSMTPCode(sIn, 250);
            
            sOut.println("xxxxxxxxxxxxxxx=="); //username 
            sOut.println("xxxxxxxxxxxxxx="); //password 
            this.nextSMTPCode(sIn, 250);
            
            sOut.println("MAIL FROM: <" + this.mailFrom + ">");
            this.nextSMTPCode(sIn, 250);
            sOut.println("RCPT TO: <" + destUser + ">");
            this.nextSMTPCode(sIn, 250);
            
            sOut.println("DATA");
            this.nextSMTPCode(sIn, 250);
            sOut.println("From: " + this.mailFrom);
            sOut.println("To: " + destUser);
            sOut.println("Subject: " + subject);
            sOut.println(message);
            
            sOut.println(".");
            this.nextSMTPCode(sIn, 250);
            
            sOut.println("QUIT");
            this.nextSMTPCode(sIn, 250);
            
            sIn.close();
            textMail = "From " + this.mailFrom + " To " + destUser + "\n" + "Subject: " + subject + "\nMessage: " + message;
            return true;
        } catch (IOException ex) {
            Logger.getLogger(MailSender.class.getName()).log(Level.SEVERE, null, ex);
        }

        return false;
    }

    public String getMail() {
        return textMail;
    }

    private void nextSMTPCode(BufferedReader sIn, int expectedCode) throws IOException {    
        int code;
        String str = sIn.readLine();
        System.out.println(str);
        String sCode = str.substring(0, 3);
        code = Integer.parseInt(sCode);

        if (code != expectedCode) {    
            sIn.close();
            System.out.println("Code: " + code + " expected code was: " + expectedCode);
            throw new IOException();
        }
    }
}

在与SMTP服务器(smtp2go)通信时遇到问题:即便在不该返回250码的场景下,也持续收到250响应码,最终导致邮件无法发送。

以下是一段通信示例:

220 mail.smtp2go.com ESMTP Exim 4.96-S2G Tue, 10 Jan 2023 14:42:02+0000
250-mail.smtp2go.com Hello  [xxxxx] 
250-SIZE 52428800 
250-8BITMIME 
250-DSN
250-PIPELINING 
250-PIPECONNECT 
250-AUTH CRAM-MD5 PLAIN LOGIN 
250-CHUNKING

之后发送的每一条命令,都会收到250-SIZE 52428800等带250码的响应。用telnet测试一切正常,问题显然出在代码中,但无法定位错误。


解决方案

核心问题:未正确处理SMTP多线响应

SMTP服务器对EHLO等命令的响应是多线格式,每条行开头都是响应码(如250),但只有最后一行的响应码后不带连字符(-),表示响应结束。你的nextSMTPCode方法仅读取了第一行响应,剩余的多线内容会留在输入缓冲区中。后续发送命令后,程序读取的其实是之前EHLO响应的剩余行,而非新命令的真实响应,因此会持续收到250开头的内容。

修复步骤

  1. 完善响应读取逻辑
    修改nextSMTPCode方法,循环读取所有响应行,直到找到不带连字符的目标响应码:

    private void nextSMTPCode(BufferedReader sIn, int expectedCode) throws IOException {    
        int code;
        String str;
        do {
            str = sIn.readLine();
            if (str == null) {
                throw new IOException("SMTP连接意外关闭");
            }
            System.out.println(str);
            String sCode = str.substring(0, 3);
            code = Integer.parseInt(sCode);
        } while (str.length() > 3 && str.charAt(3) == '-');
    
        if (code != expectedCode) {    
            sIn.close();
            System.out.println("收到响应码: " + code + " 预期响应码: " + expectedCode);
            throw new IOException();
        }
    }
    
  2. 修正命令的预期响应码
    代码中多处预期响应码错误,这也是发送失败的关键:

    • AUTH LOGIN命令的正确响应码是334(提示输入用户名),发送用户名后再次收到334(提示输入密码),密码验证成功后返回235。
    • DATA命令的响应码是354(提示开始输入邮件内容),而非250。
    • EHLO命令需要携带本地主机名,不能只发送EHLO,应改为sOut.println("EHLO " + this.localMachine);。
    • QUIT命令的响应码是221,而非250。

    修正后的trySend方法相关片段:

    this.nextSMTPCode(sIn, 220);
    sOut.println("EHLO " + this.localMachine);
    this.nextSMTPCode(sIn, 250);
    
    sOut.println("AUTH LOGIN");
    this.nextSMTPCode(sIn, 334); // 等待用户名提示
    sOut.println("xxxxxxxxxxxxxxx=="); // 用户名Base64编码
    this.nextSMTPCode(sIn, 334); // 等待密码提示
    sOut.println("xxxxxxxxxxxxxx="); // 密码Base64编码
    this.nextSMTPCode(sIn, 235); // 认证成功
    
    sOut.println("MAIL FROM: <" + this.mailFrom + ">");
    this.nextSMTPCode(sIn, 250);
    sOut.println("RCPT TO: <" + destUser + ">");
    this.nextSMTPCode(sIn, 250);
    
    sOut.println("DATA");
    this.nextSMTPCode(sIn, 354); // 等待输入内容
    sOut.println("From: " + this.mailFrom);
    sOut.println("To: " + destUser);
    sOut.println("Subject: " + subject);
    sOut.println(); // 邮件头和正文之间需要空行
    sOut.println(message);
    
    sOut.println(".");
    this.nextSMTPCode(sIn, 250);
    
    sOut.println("QUIT");
    this.nextSMTPCode(sIn, 221);
    
  3. 补充邮件格式细节
    邮件头和正文之间必须有一个空行,否则服务器可能无法正确解析邮件内容,这也是潜在的发送失败原因。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 12:25:22