Attaching to email and extracting email attachments

2,517 views
Skip to first unread message

jesse junsay

unread,
Jul 15, 2017, 10:39:28 PM7/15/17
to golang-nuts
Hi,

Been trying do email attachments in golang using net/smtp and mail packages but to no avail. I tried attaching email and extracting attachments from email using multipart but I just cant figure out how that works in golang. I have tried to look for samples online and even posting on golang forum and stackoverflow. It seems like no one has ever done this issue before. Any help will be greatly appreciated.


andrey mirtchovski

unread,
Jul 16, 2017, 12:34:34 AM7/16/17
to jesse junsay, golang-nuts
the code below which gzips and attaches a file to an email io.Writer
has worked for me for close to 5 years. it's not idiomatic go code so
don't use it verbatim, only as an example.

the boundary is something random the client chooses. i use a sha256
baked into the client. the same for every attachment.

func attach(w io.Writer, file, boundary string) {
fmt.Fprintf(w, "\n--%s\n", boundary)
contents, err := os.Open(file)
if err != nil {
fmt.Fprintf(w, "Content-Type: text/plain; charset=utf-8\n")
fmt.Fprintf(w, "could not open file: %v\n", err)
} else {
defer contents.Close()
fmt.Fprintf(w, "Content-Type: application/octet-stream\n")
fmt.Fprintf(w, "Content-Transfer-Encoding: base64\n")
fmt.Fprintf(w, "Content-Disposition: attachment;
filename=\"%s\"\n\n", filepath.Base(file)+".gz")

b64 := base64.NewEncoder(base64.StdEncoding, w)
gzip := gzip.NewWriter(b64)
io.Copy(gzip, contents)
gzip.Close()
b64.Close()
}
fmt.Fprintf(w, "\n--%s\n", boundary)
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to golang-nuts...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
Message has been deleted

jesse junsay

unread,
Jul 18, 2017, 10:22:14 PM7/18/17
to golang-nuts, jesse...@gmail.com
Hi Andrey,

Thank you. I have been trying to figure out how to use your example. But the email that I receive from it are plain text even the encoded attached file. It does not appear as attached but as encoded characters in the mail body. And there is still no attachment seen. I tried modifying your example but to no avail. I end up using your example code per code but still does not work. Please see below excerpt of my code where I call your example in a function I called "attachToEmail". I have a send mail function "sendEmail" using smpt:

I do not know which part I am missing. I have been trying to figure this out for more than a week now.

func sendEmail(email string, typeOfEmail int, lastInsertedId int) {
/*e := email.NewEmail()

e.From = "em...@email.com"
e.To = []string{emails}
e.Bcc = []string{""}
e.Cc = []string{""}
e.Subject = "Text Receipt"
e.Text = []byte("This is to confirm that we received your email.")
if IncludeEmailDump {
e.Text = []byte(getStats(emails, lastInsertedId))
}
e.HTML = []byte("<h1>Fancy HTML is supported, too!</h1>")
e.Send(EmailHostName, smtp.PlainAuth("", EmailUserName, EmailServerPassword, EmailHostName))*/

addr := EmailHostName + ":" + EmailServerPort

fromName := "emailFrom"
fromEmail := "em...@email.com"
toNames := []string{"Client"}
toEmails := []string{email}
subject := "Email"
body := "This is to confirm that we received your email."
if IncludeEmailDump {
body = body + getStats(email, lastInsertedId)
}

// Build RFC-2822 email
toAddresses := []string{}
for i := range toEmails {
mAdd := mail.Address{}
mAdd.Name = toNames[i]
mAdd.Address = toEmails[i]
toAddresses = append(toAddresses, mAdd.String())
}

toHeader := strings.Join(toAddresses, ", ")
fAdd := mail.Address{}
fAdd.Name = fromName
fAdd.Address = fromEmail
fromHeader := fAdd.String()
subjectHeader := subject
header := make(map[string]string)

header["To"] = toHeader
header["From"] = fromHeader
header["Subject"] = subjectHeader
header["Content-Type"] = `text/html; charset="UTF-8"`
msg := ""
for headerKey, headerValue := range header {
msg += fmt.Sprintf("%s: %s\r\n", headerKey, headerValue)
}
msg += "\r\n" + body
bMsg := []byte(msg)

// Send using local postfix service
smtpPostfix, err := smtp.Dial(addr)
if err != nil {
logError(err.Error())
}

defer smtpPostfix.Close()
if err = smtpPostfix.Mail(fromHeader); err != nil {
logError(err.Error())
}
for _, addr := range toEmails {
if err = smtpPostfix.Rcpt(addr); err != nil {
logError(err.Error())
}
}
smtpData, err := smtpPostfix.Data()
if err != nil {
logError(err.Error())
}

_, err = smtpData.Write(bMsg)
if err != nil {
logError(err.Error())
}

attachToEmail(smtpData, "file.txt", "boundary1234567890")

err = smtpData.Close()
if err != nil {
logError(err.Error())
}
err = smtpPostfix.Quit()
if err != nil {
logError(err.Error())
}

//attachToEmail(email)
}

func attachToEmail(writeToEmail io.Writer, filename string, boundary string) {
fmt.Fprintf(writeToEmail, "\n--%s\n", "\n\n")
fmt.Fprintf(writeToEmail, "\n--%s\n", boundary)
contents, err := os.Open(filename)
if err != nil {
fmt.Fprintf(writeToEmail, "Content-Type: text/plain; charset=utf-8\n")
fmt.Fprintf(writeToEmail, "could not open file: %v\n", err)
} else {
defer contents.Close()
//fmt.Fprintf(writeToEmail, "Content-Type: application/octet-stream\n")
fmt.Fprintf(writeToEmail, "Content-Type: application/octet-stream; charset=utf-8\n")
fmt.Fprintf(writeToEmail, "Content-Transfer-Encoding: base64\n")
fmt.Fprintf(writeToEmail, "Content-Disposition: attachment;filename=\"%s\"\n\n", filepath.Base(filename)+".gz")
fmt.Fprintf(writeToEmail, "\n--%s\n", boundary)
fmt.Fprintf(writeToEmail, "\n\n\n")

b64 := base64.NewEncoder(base64.StdEncoding, writeToEmail)
gzip := gzip.NewWriter(b64)
io.Copy(gzip, contents)
//io.Copy(b64, contents)
gzip.Close()
b64.Close()
}
fmt.Fprintf(writeToEmail, "\n--%s\n", boundary)
}

andrey mirtchovski

unread,
Jul 18, 2017, 11:39:32 PM7/18/17
to jesse junsay, golang-nuts
the example you have given is incomplete, but the most likely reason
you're not being successful is that you are not setting correctly
headers for multipart/mixed mime types. i've created a full working
example here, hopefully this helps:

https://play.golang.org/p/xVqUDN7OGt

for more info on multipart formats:
https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html

jesse junsay

unread,
Jul 19, 2017, 11:44:45 AM7/19/17
to andrey mirtchovski, golang-nuts
Hey Andrey!!!

That worked, thank you for your example. At least that cleared that part out in my mind. And all those other parts like the multipart. Makes sense now. Learned a lot too...

Thank you very much I really appreciate it... It already took me more than a week. My first time working on this. I do not know how long it would have taken me without your help.

Thanks a lot.!!!

jesse junsay

unread,
Jul 25, 2017, 11:09:25 PM7/25/17
to andrey mirtchovski, golang-nuts
Hi Andrey,

I was wondering if you have a sample on how to extract the attachment from email and save it to disk/file. I tried reverse engineer the sample code of attaching attachments into an email but cant figure out what to do with the []byte. 

Have you tried doing this before? Just the opposite of attaching to emails. This one is taking the attachments from email and save them accordingly to the harddrive.

andrey mirtchovski

unread,
Jul 26, 2017, 1:04:32 AM7/26/17
to jesse junsay, golang-nuts
I have not done this. It seems relatively easy given the boundary
delimiter and some of the mime header information to pick out the file
contents by searching forward through the []byte to a delimiter and
writing the contents from there to the next delimiter to a file, but I
have not done this.

You may consider using something more easily parsable as message
content if you're both the sender and the receiver...

jesse junsay

unread,
Jul 26, 2017, 1:19:24 AM7/26/17
to andrey mirtchovski, golang-nuts
Yes, I've actually tried doing that... but resulting file is gibberish... I might have not done the decoding correctly... Anyway... Thank you... 

Tamás Gulácsi

unread,
Jul 26, 2017, 1:35:15 AM7/26/17
to golang-nuts
Check out github.com/tgulacsi/agostle - it walks the tree of mime parts of mail and converts everything to PDF, but you need the walk part only - which uses mime/multipart reader basically.

jesse junsay

unread,
Jul 26, 2017, 2:52:32 AM7/26/17
to Tamás Gulácsi, golang-nuts
Thank you Tamas... I am already done with identifying each part using the mime multipart... My main issue now is decoding it back to its binary form and save it to disk... each attachment to its own data format. jpg attachment to file.jpg, pdf attachment to file.pdf and txt attachment to file.txt...

On Wed, Jul 26, 2017 at 1:35 PM, Tamás Gulácsi <tgula...@gmail.com> wrote:
Check out github.com/tgulacsi/agostle - it walks the tree of mime parts of mail and converts everything to PDF, but you need the walk part only - which uses mime/multipart reader basically.

--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/nfJyUx3BVvs/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts+unsubscribe@googlegroups.com.

Konstantin Khomoutov

unread,
Jul 26, 2017, 4:20:37 AM7/26/17
to golang-nuts
On Wed, Jul 26, 2017 at 02:52:05PM +0800, jesse junsay wrote:

> > Check out github.com/tgulacsi/agostle - it walks the tree of mime parts
> > of mail and converts everything to PDF, but you need the walk part only -
> > which uses mime/multipart reader basically.
> Thank you Tamas... I am already done with identifying each part using the
> mime multipart... My main issue now is decoding it back to its binary form
> and save it to disk... each attachment to its own data format. jpg
> attachment to file.jpg, pdf attachment to file.pdf and txt attachment to
> file.txt...

You should study a bit of theory behind this.

As the first step, you can take any of your sample e-mail messages and
ask your mail reading program to show you the "raw" message's view.
You can also save the message as a file to your filesystem and then
use any "advanced" text viewer program (say, Notepad++ would do) to view
its "raw" content. (If asked, save as "mbox" format or "EML" format.)

You can then see that each MIME part of your message has individual
header block followed by an empty line (sole CR+LF sequence) followed by
the body of that part. This header contains several standard fields
defining what's the encoding of the following body, and what's the
format of the source data which was encoded. Say, typically, the
encoding is Base64, and the format depends on the data -- it may be,
say, text/json or image/jpeg and so on.

Your task is to try and parse these fields as you iterate over the MIME
parts of your message and act accordingly.
The standard library has all the bits you need to carry out this task --
see [1, 2].

1. https://golang.org/pkg/mime/
2. https://golang.org/pkg/mime/multipart/

Gulácsi Tamás

unread,
Jul 26, 2017, 5:31:26 AM7/26/17
to jesse junsay, golang-nuts
You only need to decode the Transfer-Encoding, and treate the file as Content-Type says.

To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.

jesse junsay

unread,
Jul 26, 2017, 7:04:12 AM7/26/17
to Gulácsi Tamás, golang-nuts
@Konstantin K. - Yes been doing that. Have you successfully done this problem before?

@Gulacsi - That's where I am right now. 

Thanks guys. Will let you know if I figure this out...

To unsubscribe from this group and all its topics, send an email to golang-nuts+unsubscribe@googlegroups.com.

jesse junsay

unread,
Jul 30, 2017, 1:12:16 PM7/30/17
to Gulácsi Tamás, golang-nuts
Hi Guys,

Last week finally was able to resolve my issue. After looping through the parts and determining which are attachments. All I just need to do is read the file into io.Reader type then decode it and convert it back to []byte and write to file.

b64 := base64.NewDecoder(base64.StdEncoding, typeIOReader) // Decode

err = ioutil.WriteFile(filename, StreamToByte(b64), 0644)

if err != nil {
log.Fatal(err)
}

I am not sure if this is the most efficient way of doing this. Would be glad for any suggestions.

Thanks guys...

Gulácsi Tamás

unread,
Jul 30, 2017, 1:17:55 PM7/30/17
to jesse junsay, golang-nuts

If you can decod partially, then Read from the Body and Write to the file. If it is more complec, then see golang.org/x/text/encoding.Transformer


To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.

jesse junsay

unread,
Jul 30, 2017, 1:20:24 PM7/30/17
to Gulácsi Tamás, golang-nuts
Thank you. Will look into it.

To unsubscribe from this group and all its topics, send an email to golang-nuts+unsubscribe@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages