In golang, I am trying to send an email with an XLSX file attached.
I use github.com/tealeg/xlsx/v3
to generate the XLSX file as a byte array and it's working well when served with a web server (here gin) like that:
c.Header("Content-Description", "File Transfer")
c.Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
c.Header("Content-Disposition", "attachment; filename="+time.Now().UTC().Format("daily-alerts-20060102.xlsx"))
c.Data(http.StatusOK, "application/octet-stream", fileContent)
But, when sent with SES, whereas a CSV file is served correctly, the 6ko XLSX file turns into a 10ko XLSX file, and it's impossible to open it with Excel:
func (s *EmailSenderParams) SendEmailWithAttachment(content string, data *models.RawEmailData, attachment []byte, attachmentFilename string) error {
sess, err := session.NewSession(&aws.Config{
Region: aws.String(s.awsRegion)},
)
creds := credentials.NewStaticCredentials(s.apiID, s.apiKey, "")
// Create an SES session.
svc := ses.New(sess, &aws.Config{Credentials: creds})
// Assemble the email.
buf := new(bytes.Buffer)
writer := multipart.NewWriter(buf)
// email main header:
h := make(textproto.MIMEHeader)
//h.Set("From", source)
h.Set("To", data.ReceiverMail)
//h.Set("Return-Path", source)
h.Set("Subject", data.Subject)
h.Set("Content-Language", "en-US")
h.Set("Content-Type", "multipart/mixed; boundary=\""+writer.Boundary()+"\"")
h.Set("MIME-Version", "1.0")
_, err = writer.CreatePart(h)
if err != nil {
return err
}
// body:
h = make(textproto.MIMEHeader)
h.Set("Content-Transfer-Encoding", "7bit")
h.Set("Content-Type", "text/plain; charset=us-ascii")
part, err := writer.CreatePart(h)
if err != nil {
return err
}
_, err = part.Write([]byte(content))
if err != nil {
return err
}
// file attachment:
h = make(textproto.MIMEHeader)
h.Set("Content-Disposition", "attachment; filename="+attachmentFilename)
h.Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; x-unix-mode=0644; name=\""+attachmentFilename+"\"")
h.Set("Content-Transfer-Encoding", "7bit")
part, err = writer.CreatePart(h)
if err != nil {
return err
}
_, err = part.Write(attachment)
if err != nil {
return err
}
err = writer.Close()
if err != nil {
return err
}
// Strip boundary line before header (doesn't work with it present)
st := buf.String()
if strings.Count(st, "\n") < 2 {
return fmt.Errorf("invalid e-mail content")
}
st = strings.SplitN(st, "\n", 2)[1]
raw := ses.RawMessage{
Data: []byte(st),
}
input := &ses.SendRawEmailInput{
Destinations: []*string{aws.String(data.ReceiverMail)},
Source: aws.String(s.senderEmail),
RawMessage: &raw,
}
// Attempt to send the email.
_, err = svc.SendRawEmail(input)
// Display error messages if they occur.
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case ses.ErrCodeMessageRejected:
logrus.Warnln(ses.ErrCodeMessageRejected, aerr.Error())
case ses.ErrCodeMailFromDomainNotVerifiedException:
logrus.Warnln(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error())
case ses.ErrCodeConfigurationSetDoesNotExistException:
logrus.Warnln(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error())
default:
logrus.Warnln(aerr.Error())
}
} else {
logrus.Warnln(err.Error())
}
logrus.Warnln(err)
return err
}
logrus.Infoln("SES Email Sent to " + data.ReceiverName + " at address: " + data.ReceiverMail)
return nil
}
I think there might be something wrong with MIMEHeaders, or with multipart or encoding, but I am strugling to find what. Do you know any successful method to send an email with an XLSX file attached with AWS SES?
The solution was to base64 encode the file, and set Content-Transfer-Encoding
header to base64
h.Set("Content-Transfer-Encoding", "base64")
part, err = writer.CreatePart(h)
if err != nil {
return err
}
b := make([]byte, base64.StdEncoding.EncodedLen(len(attachment)))
base64.StdEncoding.Encode(b, attachment)
_, err = part.Write(b)