Golang中如何将发送的邮件保存到邮件客户端的"已发送"文件夹

Golang中如何将发送的邮件保存到邮件客户端的"已发送"文件夹 我正在开发一个电子邮件客户端,它分别使用SMTP包(go-mail)和IMAP包(emersion)来发送和接收邮件。IMAP可以从电子邮件服务器读取邮件。但是,SMTP包在发送邮件时不会将邮件保存到“已发送”文件夹中。如何使用我正在使用的包来实现这个功能?或者是否有其他支持此功能的包?我们尝试过使用IMAP的Append选项,它可以将已发送的邮件存储在“已发送”文件夹中,但它没有“Message_Id”和其他通常在读取邮件时可用的头部信息。

我看到使用PHP开发的Roundcube电子邮件客户端中有相同的功能选项。我不知道如何在Golang中实现这一点。

如果有人能提供更好的解决方案,那将非常有帮助。

提前感谢。


更多关于Golang中如何将发送的邮件保存到邮件客户端的"已发送"文件夹的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中如何将发送的邮件保存到邮件客户端的"已发送"文件夹的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Golang中实现将已发送邮件保存到IMAP服务器的"已发送"文件夹,可以通过以下步骤完成:

核心解决方案

使用go-imap包的Append方法将已发送邮件存储到IMAP服务器,并确保包含完整的邮件头部信息。

实现示例

package main

import (
    "bytes"
    "fmt"
    "log"
    "time"

    "github.com/emersion/go-imap"
    "github.com/emersion/go-imap/client"
    "github.com/emersion/go-message/mail"
)

// SendAndSaveEmail 发送邮件并保存到已发送文件夹
func SendAndSaveEmail(smtpClient *smtp.Client, imapClient *client.Client, 
    from, to, subject, body string) error {
    
    // 1. 构建完整的邮件内容(包含头部)
    var msgBuffer bytes.Buffer
    
    // 创建邮件头部
    h := mail.Header{}
    h.Set("From", from)
    h.Set("To", to)
    h.Set("Subject", subject)
    h.Set("Date", time.Now().Format(time.RFC1123Z))
    h.Set("Message-ID", generateMessageID(from))
    h.Set("MIME-Version", "1.0")
    h.Set("Content-Type", "text/plain; charset=utf-8")
    
    // 写入头部
    if err := mail.WriteHeader(&msgBuffer, h); err != nil {
        return fmt.Errorf("写入邮件头部失败: %v", err)
    }
    
    // 写入邮件正文
    msgBuffer.WriteString(body)
    
    // 2. 使用SMTP发送邮件
    msgBytes := msgBuffer.Bytes()
    if err := sendViaSMTP(smtpClient, from, to, msgBytes); err != nil {
        return fmt.Errorf("SMTP发送失败: %v", err)
    }
    
    // 3. 保存到IMAP的已发送文件夹
    if err := saveToSentFolder(imapClient, msgBytes); err != nil {
        return fmt.Errorf("保存到已发送文件夹失败: %v", err)
    }
    
    return nil
}

// saveToSentFolder 将邮件保存到已发送文件夹
func saveToSentFolder(imapClient *client.Client, msg []byte) error {
    // 选择已发送文件夹
    sentFolder := "Sent"
    if err := imapClient.Select(sentFolder, false); err != nil {
        // 如果文件夹不存在,尝试创建
        if err := imapClient.Create(sentFolder); err != nil {
            return fmt.Errorf("创建已发送文件夹失败: %v", err)
        }
        if err := imapClient.Select(sentFolder, false); err != nil {
            return fmt.Errorf("选择已发送文件夹失败: %v", err)
        }
    }
    
    // 设置邮件标志
    flags := []string{imap.SeenFlag, imap.DraftFlag}
    date := time.Now()
    
    // 使用Append保存邮件
    if err := imapClient.Append(sentFolder, flags, date, imap.Literal(msg)); err != nil {
        return fmt.Errorf("追加邮件到已发送文件夹失败: %v", err)
    }
    
    return nil
}

// generateMessageID 生成唯一的Message-ID
func generateMessageID(from string) string {
    return fmt.Sprintf("<%d.%s@localhost>", time.Now().UnixNano(), from)
}

// sendViaSMTP 通过SMTP发送邮件
func sendViaSMTP(smtpClient *smtp.Client, from, to string, msg []byte) error {
    // 这里使用你的SMTP发送逻辑
    // 示例使用net/smtp:
    // return smtp.SendMail(server, auth, from, []string{to}, msg)
    return nil
}

完整邮件构建示例

package main

import (
    "bytes"
    "fmt"
    "mime"
    "time"

    "github.com/emersion/go-message"
    "github.com/emersion/go-message/mail"
)

// BuildCompleteEmail 构建包含完整头部的邮件
func BuildCompleteEmail(from, to, subject, body string) ([]byte, error) {
    var buf bytes.Buffer
    
    // 创建邮件头部
    h := mail.Header{}
    h.Set("From", from)
    h.Set("To", to)
    h.Set("Subject", mime.QEncoding.Encode("utf-8", subject))
    h.Set("Date", time.Now().Format(time.RFC1123Z))
    h.Set("Message-ID", fmt.Sprintf("<%d.%s>", time.Now().UnixNano(), from))
    h.Set("MIME-Version", "1.0")
    h.Set("Content-Type", "text/plain; charset=utf-8")
    h.Set("Content-Transfer-Encoding", "quoted-printable")
    
    // 创建邮件写入器
    mw, err := mail.CreateWriter(&buf, h)
    if err != nil {
        return nil, err
    }
    defer mw.Close()
    
    // 创建文本部分
    tw, err := mw.CreateInline()
    if err != nil {
        return nil, err
    }
    
    th := make(message.Header)
    th.Set("Content-Type", "text/plain; charset=utf-8")
    th.Set("Content-Transfer-Encoding", "quoted-printable")
    
    pw, err := tw.CreatePart(th)
    if err != nil {
        return nil, err
    }
    
    // 写入正文
    if _, err := pw.Write([]byte(body)); err != nil {
        return nil, err
    }
    pw.Close()
    
    return buf.Bytes(), nil
}

使用第三方包简化

考虑使用go-messagego-smtp的组合:

import (
    "github.com/emersion/go-message"
    "github.com/emersion/go-smtp"
)

// 使用go-smtp发送并自动保存
type Backend struct {
    imapClient *client.Client
}

func (b *Backend) Send(from string, to []string, r io.Reader) error {
    // 读取完整邮件
    msgBytes, err := io.ReadAll(r)
    if err != nil {
        return err
    }
    
    // 保存到已发送文件夹
    if err := b.saveToSentFolder(msgBytes); err != nil {
        return err
    }
    
    // 实际发送逻辑
    return nil
}

关键点

  1. 完整的邮件头部:确保在调用Append之前,邮件包含Message-IDDateFromTo等所有必需头部
  2. 正确的MIME格式:使用go-message/mail包构建符合标准的MIME邮件
  3. 时间戳:为Append方法提供正确的发送时间
  4. 邮件标志:设置适当的IMAP标志(如\Seen

这种方法与Roundcube的实现原理相同,都是通过IMAP的APPEND命令将已发送邮件存储到服务器。

回到顶部