Skip to content

Instantly share code, notes, and snippets.

@douglasmakey
Last active February 2, 2024 17:05
Show Gist options
  • Star 26 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save douglasmakey/90753ecf37ac10c25873825097f46300 to your computer and use it in GitHub Desktop.
Save douglasmakey/90753ecf37ac10c25873825097f46300 to your computer and use it in GitHub Desktop.
Golang - send an email with attachments.
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{"to@gmail.com"}
m.CC = []string{"copy1@gmail.com", "copy2@gmail.com"}
m.BCC = []string{"bc@gmail.com"}
m.AttachFile("/path/to/file")
fmt.Println(sender.Send(m))
}
@stvoidit
Copy link

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

@hasanAjsf
Copy link

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?

@rishab-zomato
Copy link

rishab-zomato commented Dec 19, 2020

How to add array of bcc,cc to buf?

@afrizuko
Copy link

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

@AddyM
Copy link

AddyM commented Mar 3, 2021

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))

@douglasmakey
Copy link
Author

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.

https://twitter.com/douglasmakey
douglasmakey@gmail.com

@kuncolaxmono
Copy link

kuncolaxmono commented Aug 6, 2021

Send pdf to yopmail.. can you help me, for this problem.. thanks..

Screen Shot 2021-08-06 at 11 42 27

@ryanprtma
Copy link

i tried this with mailtrap as :

Screenshot from 2022-03-09 07-28-15

but i got this

Screenshot from 2022-03-09 07-28-57

@stvoidit
Copy link

stvoidit commented Mar 9, 2022

i tried this with mailtrap as :

Screenshot from 2022-03-09 07-28-15

but i got this

Screenshot from 2022-03-09 07-28-57

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.

@bekha-io
Copy link

bekha-io commented Sep 30, 2022

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.

https://twitter.com/douglasmakey douglasmakey@gmail.com

Hi, @douglasmakey ! I'm getting the same error as the author above. This code results only empty .csv files... :(

@rahuljoshi897
Copy link

H @douglasmakey , Is there any way to include an image in the email body using the GOalng net/smtp package?

@suraneti
Copy link

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{"to@gmail.com"}
	m.CC = []string{"copy1@gmail.com", "copy2@gmail.com"}
	m.BCC = []string{"bc@gmail.com"}
	m.AttachFile("/path/to/file")
	fmt.Println(sender.Send(m))
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment