Golang中如何获取SMTP服务器的响应

Golang中如何获取SMTP服务器的响应 我正在使用Go语言的net/smtp包发送邮件。

每当我向SMTP服务器发送邮件时,我需要捕获服务器的响应,特别是DSN(投递状态通知)。

例如,我的本地SMTP服务器在邮件发送结束时返回一个"ok queued as "消息。

我需要捕获这个信息并打印到日志中。

我该如何实现?

package main

import (
    "log"
    "net/smtp"
)

func sendEmail(msg []byte) {
    c, err := smtp.Dial("localhost:25")
    if err != nil {
        log.Fatal(err)
    }
    if err := c.Mail("sender@example.org"); err != nil {
        log.Fatal(err)
    }

    if err := c.Rcpt("recipient@example.net"); err != nil {
        log.Fatal(err)
    }
    wc, err := c.Data()
    if err != nil {
        log.Fatal(err)
    }

    _, err = wc.Write(msg)
    if err != nil {
        log.Fatal(err)
    }

    //如何在此处获取响应??
    err = wc.Close()
    if err != nil {
        log.Fatal(err)
    }

    err = c.Quit()
    if err != nil {
        log.Fatal(err)
    }
}

更多关于Golang中如何获取SMTP服务器的响应的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

您是指像这样的响应吗?

S: 250 Ok: queued as 12345

Client 的方法不会提取消息短语,它们甚至将返回码抽象为 err 是否为 nil。因此,您所要求的功能无法通过 net/smtp 包实现。

由于 smtp 包已冻结,不再接受新功能,您可以在 https://godoc.org/?q=smtp 上搜索替代包。

更多关于Golang中如何获取SMTP服务器的响应的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go的net/smtp包中,可以通过自定义textproto.Conn来捕获SMTP服务器的响应。以下是实现方法:

package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
    "net/textproto"
    "strings"
)

type loggingConn struct {
    net.Conn
    responses []string
}

func (lc *loggingConn) Read(b []byte) (n int, err error) {
    n, err = lc.Conn.Read(b)
    if n > 0 {
        // 捕获响应并存储
        response := string(b[:n])
        lc.responses = append(lc.responses, response)
        
        // 打印到日志
        log.Printf("SMTP响应: %s", strings.TrimSpace(response))
    }
    return n, err
}

func sendEmailWithLogging(msg []byte) {
    // 建立TCP连接
    conn, err := net.Dial("tcp", "localhost:25")
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    // 包装连接以捕获响应
    lc := &loggingConn{Conn: conn}
    text := textproto.NewConn(lc)
    
    // 创建SMTP客户端
    c, err := smtp.NewClient(text, "localhost")
    if err != nil {
        log.Fatal(err)
    }
    defer c.Close()

    // 发送邮件命令
    if err := c.Mail("sender@example.org"); err != nil {
        log.Fatal(err)
    }

    if err := c.Rcpt("recipient@example.net"); err != nil {
        log.Fatal(err)
    }

    wc, err := c.Data()
    if err != nil {
        log.Fatal(err)
    }

    _, err = wc.Write(msg)
    if err != nil {
        log.Fatal(err)
    }

    // 关闭Data写入器,此时会收到最终响应
    err = wc.Close()
    if err != nil {
        log.Fatal(err)
    }

    // 打印所有捕获的响应
    fmt.Println("所有SMTP响应:")
    for i, resp := range lc.responses {
        fmt.Printf("[%d] %s", i+1, resp)
    }

    err = c.Quit()
    if err != nil {
        log.Fatal(err)
    }
}

// 或者使用更直接的方法,通过扩展smtp.Client
type loggingClient struct {
    *smtp.Client
    responses []string
}

func (lc *loggingClient) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
    id, err := lc.Client.Text.Cmd(format, args...)
    if err != nil {
        return 0, "", err
    }
    lc.Client.Text.StartResponse(id)
    defer lc.Client.Text.EndResponse(id)
    
    code, msg, err := lc.Client.Text.ReadResponse(expectCode)
    
    // 捕获响应
    response := fmt.Sprintf("%d %s", code, msg)
    lc.responses = append(lc.responses, response)
    log.Printf("SMTP响应: %s", response)
    
    return code, msg, err
}

func sendEmailWithClientExtension(msg []byte) {
    c, err := smtp.Dial("localhost:25")
    if err != nil {
        log.Fatal(err)
    }
    defer c.Quit()

    lc := &loggingClient{Client: c}
    
    // 使用自定义的cmd方法发送命令
    // 注意:这里需要重新实现各个方法,因为smtp.Client的方法不是导出的
    // 实际使用中可能需要更复杂的包装
    
    // 或者使用反射来包装Client
    clientValue := reflect.ValueOf(c).Elem()
    textField := clientValue.FieldByName("text")
    if textField.IsValid() {
        text := textField.Interface().(*textproto.Conn)
        
        // 通过textproto.Conn直接发送命令并读取响应
        id, err := text.Cmd("MAIL FROM:<sender@example.org>")
        if err != nil {
            log.Fatal(err)
        }
        text.StartResponse(id)
        code, msg, err := text.ReadResponse(250)
        text.EndResponse(id)
        
        log.Printf("MAIL FROM响应: %d %s", code, msg)
    }
}

对于更简单的场景,可以直接使用textproto.Conn来手动发送命令:

func sendEmailManual(msg []byte) {
    conn, err := net.Dial("tcp", "localhost:25")
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    text := textproto.NewConn(conn)
    
    // 读取欢迎消息
    _, welcome, err := text.ReadResponse(220)
    log.Printf("服务器欢迎: %s", welcome)

    // 发送EHLO
    id, err := text.Cmd("EHLO localhost")
    if err != nil {
        log.Fatal(err)
    }
    text.StartResponse(id)
    code, msgResp, err := text.ReadResponse(250)
    text.EndResponse(id)
    log.Printf("EHLO响应: %d %s", code, msgResp)

    // 发送MAIL FROM
    id, err = text.Cmd("MAIL FROM:<sender@example.org>")
    if err != nil {
        log.Fatal(err)
    }
    text.StartResponse(id)
    code, msgResp, err = text.ReadResponse(250)
    text.EndResponse(id)
    log.Printf("MAIL FROM响应: %d %s", code, msgResp)

    // 发送RCPT TO
    id, err = text.Cmd("RCPT TO:<recipient@example.net>")
    if err != nil {
        log.Fatal(err)
    }
    text.StartResponse(id)
    code, msgResp, err = text.ReadResponse(250)
    text.EndResponse(id)
    log.Printf("RCPT TO响应: %d %s", code, msgResp)

    // 发送DATA
    id, err = text.Cmd("DATA")
    if err != nil {
        log.Fatal(err)
    }
    text.StartResponse(id)
    code, msgResp, err = text.ReadResponse(354)
    text.EndResponse(id)
    log.Printf("DATA响应: %d %s", code, msgResp)

    // 发送邮件内容
    dataWriter := text.DotWriter()
    _, err = dataWriter.Write(msg)
    if err != nil {
        log.Fatal(err)
    }
    dataWriter.Close()

    // 读取DATA命令的最终响应(包含队列ID)
    code, finalMsg, err := text.ReadResponse(250)
    log.Printf("邮件发送完成响应: %d %s", code, finalMsg)
    // finalMsg包含"ok queued as ..."信息

    // 发送QUIT
    id, err = text.Cmd("QUIT")
    if err != nil {
        log.Fatal(err)
    }
    text.StartResponse(id)
    code, msgResp, err = text.ReadResponse(221)
    text.EndResponse(id)
    log.Printf("QUIT响应: %d %s", code, msgResp)
}

第一种方法通过包装网络连接来捕获所有响应,第二种方法通过扩展smtp.Client,第三种方法直接使用textproto.Conn手动处理SMTP协议。第三种方法能最直接地获取到DATA命令结束后的最终响应,其中包含你需要的队列ID信息。

回到顶部