-
-
Save douglasmakey/90753ecf37ac10c25873825097f46300 to your computer and use it in GitHub Desktop.
package main | |
import ( | |
"bytes" | |
"encoding/base64" | |
"fmt" | |
"io/ioutil" | |
"mime/multipart" | |
"net/smtp" | |
"os" | |
"path/filepath" | |
) | |
var ( | |
host = os.Getenv("EMAIL_HOST") | |
username = os.Getenv("EMAiL_USERNAME") | |
password = os.Getenv("EMAIL_PASSWORD") | |
portNumber = os.Getenv("EMAIL_PORT") | |
) | |
type Sender struct { | |
auth smtp.Auth | |
} | |
type Message struct { | |
To []string | |
CC []string | |
BCC []string | |
Subject string | |
Body string | |
Attachments map[string][]byte | |
} | |
func New() *Sender { | |
auth := smtp.PlainAuth("", username, password, host) | |
return &Sender{auth} | |
} | |
func (s *Sender) Send(m *Message) error { | |
return smtp.SendMail(fmt.Sprintf("%s:%s", host, portNumber), s.auth, username, m.To, m.ToBytes()) | |
} | |
func NewMessage(s, b string) *Message { | |
return &Message{Subject: s, Body: b, Attachments: make(map[string][]byte)} | |
} | |
func (m *Message) AttachFile(src string) error { | |
b, err := ioutil.ReadFile(src) | |
if err != nil { | |
return err | |
} | |
_, fileName := filepath.Split(src) | |
m.Attachments[fileName] = b | |
return nil | |
} | |
func (m *Message) ToBytes() []byte { | |
buf := bytes.NewBuffer(nil) | |
withAttachments := len(m.Attachments) > 0 | |
buf.WriteString(fmt.Sprintf("Subject: %s\n", m.Subject)) | |
buf.WriteString(fmt.Sprintf("To: %s\n", strings.Join(m.To, ","))) | |
if len(m.CC) > 0 { | |
buf.WriteString(fmt.Sprintf("Cc: %s\n", strings.Join(m.CC, ","))) | |
} | |
if len(m.BCC) > 0 { | |
buf.WriteString(fmt.Sprintf("Bcc: %s\n", strings.Join(m.BCC, ","))) | |
} | |
buf.WriteString("MIME-Version: 1.0\n") | |
writer := multipart.NewWriter(buf) | |
boundary := writer.Boundary() | |
if withAttachments { | |
buf.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\n", boundary)) | |
buf.WriteString(fmt.Sprintf("--%s\n", boundary)) | |
} else { | |
buf.WriteString("Content-Type: text/plain; charset=utf-8\n") | |
} | |
buf.WriteString(m.Body) | |
if withAttachments { | |
for k, v := range m.Attachments { | |
buf.WriteString(fmt.Sprintf("\n\n--%s\n", boundary)) | |
buf.WriteString(fmt.Sprintf("Content-Type: %s\n", http.DetectContentType(v))) | |
buf.WriteString("Content-Transfer-Encoding: base64\n") | |
buf.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=%s\n", k)) | |
b := make([]byte, base64.StdEncoding.EncodedLen(len(v))) | |
base64.StdEncoding.Encode(b, v) | |
buf.Write(b) | |
buf.WriteString(fmt.Sprintf("\n--%s", boundary)) | |
} | |
buf.WriteString("--") | |
} | |
return buf.Bytes() | |
} | |
func main() { | |
sender := New() | |
m := NewMessage("Test", "Body message.") | |
m.To = []string{"[email protected]"} | |
m.CC = []string{"[email protected]", "[email protected]"} | |
m.BCC = []string{"[email protected]"} | |
m.AttachFile("/path/to/file") | |
fmt.Println(sender.Send(m)) | |
} |
I tried with with O365 email as:
var (
host = "smtp.office365.com" // os.Getenv("EMAIL_HOST")
username = "xxxx" // os.Getenv("EMAiL_USERNAME")
password = "xxxx" // os.Getenv("EMAIL_PASSWORD")
portNumber = 587 // os.Getenv("EMAIL_PORT")
)
But got the below error:
dial tcp: lookup tcp/%!s(int=587): getaddrinfow: The specified class was not found.
The I fixed the port to be:
portNumber = "587"
but I got:
504 5.7.4 Unrecognized authentication type [GV0P278CA0003.CHEP278.PROD.OUTLOOK.COM]
Any thought?
How to add array of bcc,cc to buf?
How about simply adding headers for Bcc and Cc just like Subject like below.
buf.WriteString(fmt.Sprintf("Cc: %s\n", m.Ccied)) //Cc
buf.WriteString(fmt.Sprintf("Bcc: %s\n", m.Bccied)) //Bcc
Hi ,
With adding a CSV file as an attachment , it seems we need to change the MIME type?
As i am getting the empty csv file with the email sent with above code
even with below change getting empty csv file as an attachment
buf.WriteString(fmt.Sprintf("\n\n--%s\n", boundary))
>>buf.WriteString("Content-Type: text/csv\n")
buf.WriteString("Content-Transfer-Encoding: base64\n")
buf.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=%s\n", k))
Hi @AddyM I made a small change that should help you with that, I tested it and it worked, also you can contact me directly, I will gladly give you a hand.
i tried this with mailtrap as :
but i got this
https://github.com/stvoidit/gosmtp
Please try my version based on the example of this code. Due to the specific situation, I have to use my own implementation, because popular solutions for some reason do not work, although I have implemented very basic things and perhaps not in the best way, but it works for me.
Hi @AddyM I made a small change that should help you with that, I tested it and it worked, also you can contact me directly, I will gladly give you a hand.
Hi, @douglasmakey ! I'm getting the same error as the author above. This code results only empty .csv files... :(
H @douglasmakey , Is there any way to include an image in the email body using the GOalng net/smtp
package?
io/ioutils
has deprecated after go 1.16
package main
import (
"bytes"
"encoding/base64"
"fmt"
"mime/multipart"
"net/smtp"
"os"
"path/filepath"
)
var (
host = os.Getenv("EMAIL_HOST")
username = os.Getenv("EMAIL_USERNAME")
password = os.Getenv("EMAIL_PASSWORD")
portNumber = os.Getenv("EMAIL_PORT")
)
type Sender struct {
auth smtp.Auth
}
type Message struct {
To []string
CC []string
BCC []string
Subject string
Body string
Attachments map[string][]byte
}
func New() *Sender {
auth := smtp.PlainAuth("", username, password, host)
return &Sender{auth}
}
func (s *Sender) Send(m *Message) error {
return smtp.SendMail(fmt.Sprintf("%s:%s", host, portNumber), s.auth, username, m.To, m.ToBytes())
}
func NewMessage(s, b string) *Message {
return &Message{Subject: s, Body: b, Attachments: make(map[string][]byte)}
}
func (m *Message) AttachFile(src string) error {
b, err := os.ReadFile(src)
if err != nil {
return err
}
_, fileName := filepath.Split(src)
m.Attachments[fileName] = b
return nil
}
func (m *Message) ToBytes() []byte {
buf := bytes.NewBuffer(nil)
withAttachments := len(m.Attachments) > 0
buf.WriteString(fmt.Sprintf("Subject: %s\n", m.Subject))
buf.WriteString(fmt.Sprintf("To: %s\n", strings.Join(m.To, ",")))
if len(m.CC) > 0 {
buf.WriteString(fmt.Sprintf("Cc: %s\n", strings.Join(m.CC, ",")))
}
if len(m.BCC) > 0 {
buf.WriteString(fmt.Sprintf("Bcc: %s\n", strings.Join(m.BCC, ",")))
}
buf.WriteString("MIME-Version: 1.0\n")
writer := multipart.NewWriter(buf)
boundary := writer.Boundary()
if withAttachments {
buf.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\n", boundary))
buf.WriteString(fmt.Sprintf("--%s\n", boundary))
} else {
buf.WriteString("Content-Type: text/plain; charset=utf-8\n")
}
buf.WriteString(m.Body)
if withAttachments {
for k, v := range m.Attachments {
buf.WriteString(fmt.Sprintf("\n\n--%s\n", boundary))
buf.WriteString(fmt.Sprintf("Content-Type: %s\n", http.DetectContentType(v)))
buf.WriteString("Content-Transfer-Encoding: base64\n")
buf.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=%s\n", k))
b := make([]byte, base64.StdEncoding.EncodedLen(len(v)))
base64.StdEncoding.Encode(b, v)
buf.Write(b)
buf.WriteString(fmt.Sprintf("\n--%s", boundary))
}
buf.WriteString("--")
}
return buf.Bytes()
}
func main() {
sender := New()
m := NewMessage("Test", "Body message.")
m.To = []string{"[email protected]"}
m.CC = []string{"[email protected]", "[email protected]"}
m.BCC = []string{"[email protected]"}
m.AttachFile("/path/to/file")
fmt.Println(sender.Send(m))
}
Just wanna say thank you so much for this
If anybody is passing by, you can use
mime.TypeByExtension(filepath.Ext(filename))
instead of http.DetectContentType(byteSlice)
.
You'll likely have better results as the http function supports less MIME types.
Be aware, though, that the first function (from package mime
) uses the file extension, which means that it can be tricked easily.
On the other hand, it seems that some file types have the same beginning bytes which will confuse the http
function (.docx
, .xls
, etc apparently are mistaken for .zip
, iirc).
Double check the last info but that should give you a good starting point to fix some of the issues people seemed to encounter in the comments above.
Thanks for your code. A few days spent looking for a good solution, there were problems with sending files and my mail service (yandex.ru). slightly redesigned your code for yourself, in the standard version does not work on SSL.
You can see my fork if you interesting. Thx