initial version

This commit is contained in:
admin 2022-10-17 16:43:55 +08:00
commit 408aa413e2
4 changed files with 246 additions and 0 deletions

191
email.go Normal file
View File

@ -0,0 +1,191 @@
// Package email allows to send emails with attachments.
package email
import (
"bytes"
"encoding/base64"
"fmt"
"io/ioutil"
"mime"
"net/mail"
"net/smtp"
"path/filepath"
"strings"
"time"
)
// Attachment represents an email attachment.
type Attachment struct {
Filename string
Data []byte
Inline bool
}
// Message represents a smtp message.
type Message struct {
From mail.Address
To []string
Cc []string
Bcc []string
ReplyTo string
Subject string
Body string
BodyContentType string
Attachments map[string]*Attachment
}
func (m *Message) attach(file string, inline bool) error {
data, err := ioutil.ReadFile(file)
if err != nil {
return err
}
_, filename := filepath.Split(file)
m.Attachments[filename] = &Attachment{
Filename: filename,
Data: data,
Inline: inline,
}
return nil
}
// AttachBuffer attaches a binary attachment.
func (m *Message) AttachBuffer(filename string, buf []byte, inline bool) error {
m.Attachments[filename] = &Attachment{
Filename: filename,
Data: buf,
Inline: inline,
}
return nil
}
// Attach attaches a file.
func (m *Message) Attach(file string) error {
return m.attach(file, false)
}
// Inline includes a file as an inline attachment.
func (m *Message) Inline(file string) error {
return m.attach(file, true)
}
func newMessage(subject string, body string, bodyContentType string) *Message {
m := &Message{Subject: subject, Body: body, BodyContentType: bodyContentType}
m.Attachments = make(map[string]*Attachment)
return m
}
// NewMessage returns a new Message that can compose an email with attachments
func NewMessage(subject string, body string) *Message {
return newMessage(subject, body, "text/plain")
}
// NewHTMLMessage returns a new Message that can compose an HTML email with attachments
func NewHTMLMessage(subject string, body string) *Message {
return newMessage(subject, body, "text/html")
}
// Tolist returns all the recipients of the email
func (m *Message) Tolist() []string {
tolist := m.To
for _, cc := range m.Cc {
tolist = append(tolist, cc)
}
for _, bcc := range m.Bcc {
tolist = append(tolist, bcc)
}
return tolist
}
// Bytes returns the mail data
func (m *Message) Bytes() []byte {
buf := bytes.NewBuffer(nil)
buf.WriteString("From: " + m.From.String() + "\r\n")
t := time.Now()
buf.WriteString("Date: " + t.Format(time.RFC1123Z) + "\r\n")
buf.WriteString("To: " + strings.Join(m.To, ",") + "\r\n")
if len(m.Cc) > 0 {
buf.WriteString("Cc: " + strings.Join(m.Cc, ",") + "\r\n")
}
//fix Encode
var coder = base64.StdEncoding
var subject = "=?UTF-8?B?" + coder.EncodeToString([]byte(m.Subject)) + "?="
buf.WriteString("Subject: " + subject + "\r\n")
if len(m.ReplyTo) > 0 {
buf.WriteString("Reply-To: " + m.ReplyTo + "\r\n")
}
buf.WriteString("MIME-Version: 1.0\r\n")
boundary := "f46d043c813270fc6b04c2d223da"
if len(m.Attachments) > 0 {
buf.WriteString("Content-Type: multipart/mixed; boundary=" + boundary + "\r\n")
buf.WriteString("\r\n--" + boundary + "\r\n")
}
buf.WriteString(fmt.Sprintf("Content-Type: %s; charset=utf-8\r\n\r\n", m.BodyContentType))
buf.WriteString(m.Body)
buf.WriteString("\r\n")
if len(m.Attachments) > 0 {
for _, attachment := range m.Attachments {
buf.WriteString("\r\n\r\n--" + boundary + "\r\n")
if attachment.Inline {
buf.WriteString("Content-Type: message/rfc822\r\n")
buf.WriteString("Content-Disposition: inline; filename=\"" + attachment.Filename + "\"\r\n\r\n")
buf.Write(attachment.Data)
} else {
ext := filepath.Ext(attachment.Filename)
mimetype := mime.TypeByExtension(ext)
if mimetype != "" {
mime := fmt.Sprintf("Content-Type: %s\r\n", mimetype)
buf.WriteString(mime)
} else {
buf.WriteString("Content-Type: application/octet-stream\r\n")
}
buf.WriteString("Content-Transfer-Encoding: base64\r\n")
buf.WriteString("Content-Disposition: attachment; filename=\"=?UTF-8?B?")
buf.WriteString(coder.EncodeToString([]byte(attachment.Filename)))
buf.WriteString("?=\"\r\n\r\n")
b := make([]byte, base64.StdEncoding.EncodedLen(len(attachment.Data)))
base64.StdEncoding.Encode(b, attachment.Data)
// write base64 content in lines of up to 76 chars
for i, l := 0, len(b); i < l; i++ {
buf.WriteByte(b[i])
if (i+1)%76 == 0 {
buf.WriteString("\r\n")
}
}
}
buf.WriteString("\r\n--" + boundary)
}
buf.WriteString("--")
}
return buf.Bytes()
}
// Send sends the message.
func Send(addr string, auth smtp.Auth, m *Message) error {
return smtp.SendMail(addr, auth, m.From.Address, m.Tolist(), m.Bytes())
}

18
email_test.go Normal file
View File

@ -0,0 +1,18 @@
package email
import (
"strings"
"testing"
)
func TestAttachment(t *testing.T) {
m := NewMessage("Hi", "this is the body")
if err := m.AttachBuffer("test.ics", []byte("test"), false); err != nil {
t.Fatal(err)
}
if strings.Contains(string(m.Bytes()), "text/calendar") == false {
t.Fatal("Issue with mailer")
}
}

34
example/main.go Normal file
View File

@ -0,0 +1,34 @@
package main
import (
"log"
"net/mail"
"net/smtp"
"strconv"
"qoobing.com/gomod/email"
)
func main() {
fromAddr := "ubbeybox@yqtc.com"
fromPass := "yqtc@018ubox"
fromSmtpAddr := "smtp.gmail.com"
fromSmtpPort := 587 //465;//587
toAddr := "q.bryant@live.com"
// compose the message
m := email.NewMessage("Hi", "this is the body")
m.From = mail.Address{Name: "From", Address: fromAddr}
m.To = []string{toAddr}
// add attachments
if err := m.Attach("email.go"); err != nil {
log.Fatal(err)
}
// send it
auth := smtp.PlainAuth("", fromAddr, fromPass, fromSmtpAddr)
if err := email.Send(fromSmtpAddr+":"+strconv.Itoa(fromSmtpPort), auth, m); err != nil {
log.Fatal(err)
} else {
log.Println("done")
}
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module qoobing.com/gomod/email
go 1.14