golang mail 实现ssl发送邮件(email,golang,ssl,编程语言)

时间:2024-05-06 20:41:47 作者 : 石家庄SEO 分类 : 编程语言
  • TAG :

         由于我们使用的阿里云服务器不能telnet 25端口,发送ssl时候感觉很蹩脚,就自己写了一个go mail 发送


1、文档结构

golang mail 实现ssl发送邮件

2、main.go

// main.gopackage mainimport ( "flag" "fmt" "io/ioutil" "log" "net/smtp" "os" "sslmail/mymail" "strings")func SendMail(fromuser, password, subject, host, port, bodyfile, bodyhtmlfile string, tousers, attachs []string) error { // NewEmail返回一个email结构体的指针 e := mymail.NewEmail() // 发件人 e.From = fromuser // 收件人(可以有多个) e.To = tousers // 邮件主题 e.Subject = subject // 解析html模板 //body := new(bytes.Buffer) if strings.TrimSpace(bodyfile) != "" { f, err := os.OpenFile(bodyfile, os.O_RDONLY, 0600) if err != nil { fmt.Println(err) } else { contentBytes, err := ioutil.ReadAll(f) if err != nil { fmt.Println("读取文件失败") } else { e.Text = contentBytes } } } if strings.TrimSpace(bodyhtmlfile) != "" { ft, err := os.OpenFile(bodyhtmlfile, os.O_RDONLY, 0600) if err != nil { fmt.Println(err) } else { htmlBytes, err := ioutil.ReadAll(ft) if err != nil { fmt.Println("读取文件失败") } else { e.HTML = htmlBytes } } } if len(attachs) > 0 { for _, v := range attachs { e.AttachFile(v) } } addr := host + ":" + port //fmt.Println(addr) // 发送邮件(如果使用QQ邮箱发送邮件的话,passwd不是邮箱密码而是授权码) return e.Send(addr, smtp.PlainAuth("", fromuser, password, host))}type sliceValue []stringfunc newSliceValue(vals []string, p *[]string) *sliceValue { *p = vals return (*sliceValue)(p)}func (s *sliceValue) Set(val string) error { *s = sliceValue(strings.Split(val, ",")) return nil}func (s *sliceValue) Get() interface{} { return []string(*s) }func (s *sliceValue) String() string { return strings.Join([]string(*s), ",") }func main() { //fromuser := "name@yourmail.com" fromuser := flag.String("fromuser", "name@yourmail.com", "sender email info") password := flag.String("password", "your password default", "sender email password") var tousers []string flag.Var(newSliceValue([]string{}, &tousers), "tousers", "your `tousers` email separated by ','") subject := flag.String("subject", "hello,world", "subject") host := flag.String("host", "smtp.qiye.163.com", "ssl url info") port := flag.String("port", "465", "ssl port") bodyfile := flag.String("bodyfile", "", "the body file your password default") bodyhtmlfile := flag.String("bodyhtmlfile", "", "the body html file your password default") var attachs []string flag.Var(newSliceValue([]string{}, &attachs), "attachs", "your `attachs` email separated by ','") //htmlbody := flag.String("htmlbody", "a html file ", "you can describe you mail") flag.Parse() //fmt.Println(attachs) err := SendMail(*fromuser, *password, *subject, *host, *port, *bodyfile, *bodyhtmlfile, tousers, attachs) if err != nil { log.Println("发送邮件失败") //log.Println(err) return } log.Println("发送邮件成功")}

3、mymail/email.go

// Package email is designed to provide an "email interface for humans."// Designed to be robust and flexible, the email package aims to make sending email easy without getting in the way.package mymailimport (    "bufio"    "bytes"    "crypto/rand"    "crypto/tls"    "encoding/base64"    "errors"    "fmt"    "io"    "log"    "math"    "math/big"    "mime"    "mime/multipart"    "mime/quotedprintable"    "net"    "net/mail"    "net/smtp"    "net/textproto"    "os"    "path/filepath"    "strings"    "time"    "unicode")const (    MaxLineLength      = 76                             // MaxLineLength is the maximum line length per RFC 2045    defaultContentType = "text/plain; charset=us-ascii" // defaultContentType is the default Content-Type according to RFC 2045, section 5.2)// ErrMissingBoundary is returned when there is no boundary given for a multipart entityvar ErrMissingBoundary = errors.New("No boundary found for multipart entity")// ErrMissingContentType is returned when there is no "Content-Type" header for a MIME entityvar ErrMissingContentType = errors.New("No Content-Type found for MIME entity")// Email is the type used for email messagestype Email struct {    ReplyTo     []string    From        string    To          []string    Bcc         []string    Cc          []string    Subject     string    Text        []byte // Plaintext message (optional)    HTML        []byte // Html message (optional)    Sender      string // override From as SMTP envelope sender (optional)    Headers     textproto.MIMEHeader    Attachments []*Attachment    ReadReceipt []string}// part is a copyable representation of a multipart.Parttype part struct {    header textproto.MIMEHeader    body   []byte}// NewEmail creates an Email, and returns the pointer to it.func NewEmail() *Email {    return &Email{Headers: textproto.MIMEHeader{}}}// trimReader is a custom io.Reader that will trim any leading// whitespace, as this can cause email imports to fail.type trimReader struct {    rd io.Reader}// Read trims off any unicode whitespace from the originating readerfunc (tr trimReader) Read(buf []byte) (int, error) {    n, err := tr.rd.Read(buf)    t := bytes.TrimLeftFunc(buf[:n], unicode.IsSpace)    n = copy(buf, t)    return n, err}// NewEmailFromReader reads a stream of bytes from an io.Reader, r,// and returns an email struct containing the parsed data.// This function expects the data in RFC 5322 format.func NewEmailFromReader(r io.Reader) (*Email, error) {    e := NewEmail()    s := trimReader{rd: r}    tp := textproto.NewReader(bufio.NewReader(s))    // Parse the main headers    hdrs, err := tp.ReadMIMEHeader()    if err != nil {        return e, err    }    // Set the subject, to, cc, bcc, and from    for h, v := range hdrs {        switch {        case h == "Subject":            e.Subject = v[0]            subj, err := (&mime.WordDecoder{}).DecodeHeader(e.Subject)            if err == nil && len(subj) > 0 {                e.Subject = subj            }            delete(hdrs, h)        case h == "To":            for _, to := range v {                tt, err := (&mime.WordDecoder{}).DecodeHeader(to)                if err == nil {                    e.To = append(e.To, tt)                } else {                    e.To = append(e.To, to)                }            }            delete(hdrs, h)        case h == "Cc":            for _, cc := range v {                tcc, err := (&mime.WordDecoder{}).DecodeHeader(cc)                if err == nil {                    e.Cc = append(e.Cc, tcc)                } else {                    e.Cc = append(e.Cc, cc)                }            }            delete(hdrs, h)        case h == "Bcc":            for _, bcc := range v {                tbcc, err := (&mime.WordDecoder{}).DecodeHeader(bcc)                if err == nil {                    e.Bcc = append(e.Bcc, tbcc)                } else {                    e.Bcc = append(e.Bcc, bcc)                }            }            delete(hdrs, h)        case h == "From":            e.From = v[0]            fr, err := (&mime.WordDecoder{}).DecodeHeader(e.From)            if err == nil && len(fr) > 0 {                e.From = fr            }            delete(hdrs, h)        }    }    e.Headers = hdrs    body := tp.R    // Recursively parse the MIME parts    ps, err := parseMIMEParts(e.Headers, body)    if err != nil {        return e, err    }    for _, p := range ps {        if ct := p.header.Get("Content-Type"); ct == "" {            return e, ErrMissingContentType        }        ct, _, err := mime.ParseMediaType(p.header.Get("Content-Type"))        if err != nil {            return e, err        }        switch {        case ct == "text/plain":            e.Text = p.body        case ct == "text/html":            e.HTML = p.body        }    }    return e, nil}// parseMIMEParts will recursively walk a MIME entity and return a []mime.Part containing// each (flattened) mime.Part found.// It is important to note that there are no limits to the number of recursions, so be// careful when parsing unknown MIME structures!func parseMIMEParts(hs textproto.MIMEHeader, b io.Reader) ([]*part, error) {    var ps []*part    // If no content type is given, set it to the default    if _, ok := hs["Content-Type"]; !ok {        hs.Set("Content-Type", defaultContentType)    }    ct, params, err := mime.ParseMediaType(hs.Get("Content-Type"))    if err != nil {        return ps, err    }    // If it's a multipart email, recursively parse the parts    if strings.HasPrefix(ct, "multipart/") {        if _, ok := params["boundary"]; !ok {            return ps, ErrMissingBoundary        }        mr := multipart.NewReader(b, params["boundary"])        for {            var buf bytes.Buffer            p, err := mr.NextPart()            if err == io.EOF {                break            }            if err != nil {                return ps, err            }            if _, ok := p.Header["Content-Type"]; !ok {                p.Header.Set("Content-Type", defaultContentType)            }            subct, _, err := mime.ParseMediaType(p.Header.Get("Content-Type"))            if err != nil {                return ps, err            }            if strings.HasPrefix(subct, "multipart/") {                sps, err := parseMIMEParts(p.Header, p)                if err != nil {                    return ps, err                }                ps = append(ps, sps...)            } else {                var reader io.Reader                reader = p                const cte = "Content-Transfer-Encoding"                if p.Header.Get(cte) == "base64" {                    reader = base64.NewDecoder(base64.StdEncoding, reader)                }                // Otherwise, just append the part to the list                // Copy the part data into the buffer                if _, err := io.Copy(&buf, reader); err != nil {                    return ps, err                }                ps = append(ps, &part{body: buf.Bytes(), header: p.Header})            }        }    } else {        // If it is not a multipart email, parse the body content as a single "part"        var buf bytes.Buffer        if _, err := io.Copy(&buf, b); err != nil {            return ps, err        }        ps = append(ps, &part{body: buf.Bytes(), header: hs})    }    return ps, nil}// Attach is used to attach content from an io.Reader to the email.// Required parameters include an io.Reader, the desired filename for the attachment, and the Content-Type// The function will return the created Attachment for reference, as well as nil for the error, if successful.func (e *Email) Attach(r io.Reader, filename string, c string) (a *Attachment, err error) {    var buffer bytes.Buffer    if _, err = io.Copy(&buffer, r); err != nil {        return    }    at := &Attachment{        Filename: filename,        Header:   textproto.MIMEHeader{},        Content:  buffer.Bytes(),    }    // Get the Content-Type to be used in the MIMEHeader    if c != "" {        at.Header.Set("Content-Type", c)    } else {        // If the Content-Type is blank, set the Content-Type to "application/octet-stream"        at.Header.Set("Content-Type", "application/octet-stream")    }    at.Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", filename))    at.Header.Set("Content-ID", fmt.Sprintf("<%s>", filename))    at.Header.Set("Content-Transfer-Encoding", "base64")    e.Attachments = append(e.Attachments, at)    return at, nil}// AttachFile is used to attach content to the email.// It attempts to open the file referenced by filename and, if successful, creates an Attachment.// This Attachment is then appended to the slice of Email.Attachments.// The function will then return the Attachment for reference, as well as nil for the error, if successful.func (e *Email) AttachFile(filename string) (a *Attachment, err error) {    f, err := os.Open(filename)    if err != nil {        return    }    defer f.Close()    ct := mime.TypeByExtension(filepath.Ext(filename))    basename := filepath.Base(filename)    return e.Attach(f, basename, ct)}// msgHeaders merges the Email's various fields and custom headers together in a// standards compliant way to create a MIMEHeader to be used in the resulting// message. It does not alter e.Headers.//// "e"'s fields To, Cc, From, Subject will be used unless they are present in// e.Headers. Unless set in e.Headers, "Date" will filled with the current time.func (e *Email) msgHeaders() (textproto.MIMEHeader, error) {    res := make(textproto.MIMEHeader, len(e.Headers)+4)    if e.Headers != nil {        for _, h := range []string{"Reply-To", "To", "Cc", "From", "Subject", "Date", "Message-Id", "MIME-Version"} {            if v, ok := e.Headers[h]; ok {                res[h] = v            }        }    }    // Set headers if there are values.    if _, ok := res["Reply-To"]; !ok && len(e.ReplyTo) > 0 {        res.Set("Reply-To", strings.Join(e.ReplyTo, ", "))    }    if _, ok := res["To"]; !ok && len(e.To) > 0 {        res.Set("To", strings.Join(e.To, ", "))    }    if _, ok := res["Cc"]; !ok && len(e.Cc) > 0 {        res.Set("Cc", strings.Join(e.Cc, ", "))    }    if _, ok := res["Subject"]; !ok && e.Subject != "" {        res.Set("Subject", e.Subject)    }    if _, ok := res["Message-Id"]; !ok {        id, err := generateMessageID()        if err != nil {            return nil, err        }        res.Set("Message-Id", id)    }    // Date and From are required headers.    if _, ok := res["From"]; !ok {        res.Set("From", e.From)    }    if _, ok := res["Date"]; !ok {        res.Set("Date", time.Now().Format(time.RFC1123Z))    }    if _, ok := res["MIME-Version"]; !ok {        res.Set("MIME-Version", "1.0")    }    for field, vals := range e.Headers {        if _, ok := res[field]; !ok {            res[field] = vals        }    }    return res, nil}func writeMessage(buff io.Writer, msg []byte, multipart bool, mediaType string, w *multipart.Writer) error {    if multipart {        header := textproto.MIMEHeader{            "Content-Type":              {mediaType + "; charset=UTF-8"},            "Content-Transfer-Encoding": {"quoted-printable"},        }        if _, err := w.CreatePart(header); err != nil {            return err        }    }    qp := quotedprintable.NewWriter(buff)    // Write the text    if _, err := qp.Write(msg); err != nil {        return err    }    return qp.Close()}// Bytes converts the Email object to a []byte representation, including all needed MIMEHeaders, boundaries, etc.func (e *Email) Bytes() ([]byte, error) {    // TODO: better guess buffer size    buff := bytes.NewBuffer(make([]byte, 0, 4096))    headers, err := e.msgHeaders()    if err != nil {        return nil, err    }    var (        isMixed       = len(e.Attachments) > 0        isAlternative = len(e.Text) > 0 && len(e.HTML) > 0    )    var w *multipart.Writer    if isMixed || isAlternative {        w = multipart.NewWriter(buff)    }    switch {    case isMixed:        headers.Set("Content-Type", "multipart/mixed;\r\n boundary="+w.Boundary())    case isAlternative:        headers.Set("Content-Type", "multipart/alternative;\r\n boundary="+w.Boundary())    case len(e.HTML) > 0:        headers.Set("Content-Type", "text/html; charset=UTF-8")        headers.Set("Content-Transfer-Encoding", "quoted-printable")    default:        headers.Set("Content-Type", "text/plain; charset=UTF-8")        headers.Set("Content-Transfer-Encoding", "quoted-printable")    }    headerToBytes(buff, headers)    _, err = io.WriteString(buff, "\r\n")    if err != nil {        return nil, err    }    // Check to see if there is a Text or HTML field    if len(e.Text) > 0 || len(e.HTML) > 0 {        var subWriter *multipart.Writer        if isMixed && isAlternative {            // Create the multipart alternative part            subWriter = multipart.NewWriter(buff)            header := textproto.MIMEHeader{                "Content-Type": {"multipart/alternative;\r\n boundary=" + subWriter.Boundary()},            }            if _, err := w.CreatePart(header); err != nil {                return nil, err            }        } else {            subWriter = w        }        // Create the body sections        if len(e.Text) > 0 {            // Write the text            if err := writeMessage(buff, e.Text, isMixed || isAlternative, "text/plain", subWriter); err != nil {                return nil, err            }        }        if len(e.HTML) > 0 {            // Write the HTML            if err := writeMessage(buff, e.HTML, isMixed || isAlternative, "text/html", subWriter); err != nil {                return nil, err            }        }        if isMixed && isAlternative {            if err := subWriter.Close(); err != nil {                return nil, err            }        }    }    // Create attachment part, if necessary    for _, a := range e.Attachments {        ap, err := w.CreatePart(a.Header)        if err != nil {            return nil, err        }        // Write the base64Wrapped content to the part        base64Wrap(ap, a.Content)    }    if isMixed || isAlternative {        if err := w.Close(); err != nil {            return nil, err        }    }    return buff.Bytes(), nil}// Send an email using the given host and SMTP auth (optional), returns any error thrown by smtp.SendMail// This function merges the To, Cc, and Bcc fields and calls the smtp.SendMail function using the Email.Bytes() output as the messagefunc (e *Email) Send(addr string, a smtp.Auth) error {    // Merge the To, Cc, and Bcc fields    to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))    to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)    for i := 0; i < len(to); i++ {        addr, err := mail.ParseAddress(to[i])        if err != nil {            return err        }        to[i] = addr.Address    }    // Check to make sure there is at least one recipient and one "From" address    if e.From == "" || len(to) == 0 {        return errors.New("Must specify at least one From address and one To address")    }    sender, err := e.parseSender()    if err != nil {        return err    }    raw, err := e.Bytes()    if err != nil {        return err    }    return SendMailUsingTLS(addr, a, sender, to, raw)}// Select and parse an SMTP envelope sender address.  Choose Email.Sender if set, or fallback to Email.From.func (e *Email) parseSender() (string, error) {    if e.Sender != "" {        sender, err := mail.ParseAddress(e.Sender)        if err != nil {            return "", err        }        return sender.Address, nil    } else {        from, err := mail.ParseAddress(e.From)        if err != nil {            return "", err        }        return from.Address, nil    }}// Attachment is a struct representing an email attachment.// Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in questiontype Attachment struct {    Filename string    Header   textproto.MIMEHeader    Content  []byte}// base64Wrap encodes the attachment content, and wraps it according to RFC 2045 standards (every 76 chars)// The output is then written to the specified io.Writerfunc base64Wrap(w io.Writer, b []byte) {    // 57 raw bytes per 76-byte base64 line.    const maxRaw = 57    // Buffer for each line, including trailing CRLF.    buffer := make([]byte, MaxLineLength+len("\r\n"))    copy(buffer[MaxLineLength:], "\r\n")    // Process raw chunks until there's no longer enough to fill a line.    for len(b) >= maxRaw {        base64.StdEncoding.Encode(buffer, b[:maxRaw])        w.Write(buffer)        b = b[maxRaw:]    }    // Handle the last chunk of bytes.    if len(b) > 0 {        out := buffer[:base64.StdEncoding.EncodedLen(len(b))]        base64.StdEncoding.Encode(out, b)        out = append(out, "\r\n"...)        w.Write(out)    }}// headerToBytes renders "header" to "buff". If there are multiple values for a// field, multiple "Field: value\r\n" lines will be emitted.func headerToBytes(buff io.Writer, header textproto.MIMEHeader) {    for field, vals := range header {        for _, subval := range vals {            // bytes.Buffer.Write() never returns an error.            io.WriteString(buff, field)            io.WriteString(buff, ": ")            // Write the encoded header if needed            switch {            case field == "Content-Type" || field == "Content-Disposition":                buff.Write([]byte(subval))            default:                buff.Write([]byte(mime.QEncoding.Encode("UTF-8", subval)))            }            io.WriteString(buff, "\r\n")        }    }}var maxBigInt = big.NewInt(math.MaxInt64)// generateMessageID generates and returns a string suitable for an RFC 2822// compliant Message-ID, e.g.:// <1444789264909237300.3464.1819418242800517193@DESKTOP01>//// The following parameters are used to generate a Message-ID:// - The nanoseconds since Epoch// - The calling PID// - A cryptographically random int64// - The sending hostnamefunc generateMessageID() (string, error) {    t := time.Now().UnixNano()    pid := os.Getpid()    rint, err := rand.Int(rand.Reader, maxBigInt)    if err != nil {        return "", err    }    h, err := os.Hostname()    // If we can't get the hostname, we'll use localhost    if err != nil {        h = "localhost.localdomain"    }    msgid := fmt.Sprintf("<%d.%d.%d@%s>", t, pid, rint, h)    return msgid, nil}func Dial(addr string) (*smtp.Client, error) {    conn, err := tls.Dial("tcp", addr, nil)    if err != nil {        log.Println("Dialing Error:", err)        return nil, err    }    //分解主机端口字符串    host, _, _ := net.SplitHostPort(addr)    return smtp.NewClient(conn, host)}//参考net/smtp的func SendMail()//使用net.Dial连接tls(ssl)端口时,smtp.NewClient()会卡住且不提示err//len(to)>1时,to[1]开始提示是密送func SendMailUsingTLS(addr string, auth smtp.Auth, from string,    to []string, msg []byte) (err error) {    //create smtp client    c, err := Dial(addr)    if err != nil {        log.Println("Create smpt client error:", err)        return err    }    defer c.Close()    if auth != nil {        if ok, _ := c.Extension("AUTH"); ok {            if err = c.Auth(auth); err != nil {                log.Println("Error during AUTH", err)                return err            }        }    }    if err = c.Mail(from); err != nil {        return err    }    for _, addr := range to {        if err = c.Rcpt(addr); err != nil {            return err        }    }    w, err := c.Data()    if err != nil {        return err    }    _, err = w.Write(msg)    if err != nil {        return err    }    err = w.Close()    if err != nil {        return err    }    return c.Quit()}

4、build

go build -o sslmail  main.go

5、测试,前提你的默认值要有

golang mail 实现ssl发送邮件

结果:

golang mail 实现ssl发送邮件

 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:golang mail 实现ssl发送邮件的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:CSS怎么实现文字与图片同排和文字上下垂直居中下一篇:

6 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18