Sending Raw Emails with Attachments using AWS SES in Go

Sending Raw Emails with Attachments using AWS SES in Go

ยท

5 min read

Email communication is a crucial aspect of many applications, and Amazon Simple Email Service (SES) provides a reliable and scalable solution for sending emails in the Amazon Web Services (AWS) environment. I've created a go package that facilitates sending raw emails with attachments using AWS SES.

Full Code

package oaws

import (
    "fmt"
    "io/ioutil"
    "os"

    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/rs/zerolog/log"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/awserr"
    "github.com/aws/aws-sdk-go/service/ses"
)
func NewSession(region string) (sess *session.Session, err error) {
    if region == "" {
        log.Fatal().Msg("Missing region in oaws.NewSession")
    }
    sess, err = session.NewSession(&aws.Config{
        Region: aws.String(region)})
    return
}

func SendRawEmailWithAttachment(sender, recipient, subject, html, text, fileName, region string, attachmentFile *os.File) error {
    // Create a new session using AWS SDK
    sess, err := NewSession(region)

    if err != nil {
        fmt.Println(err.Error())
        return err
    }

    _, err = attachmentFile.Seek(0, 0)
    if err != nil {
        fmt.Println("Error seeking to the beginning of the file:", err)
        return err
    }

    // Read the content of the attachment file
    attachmentContent, err := ioutil.ReadAll(attachmentFile)
    if err != nil {
        fmt.Println("Error reading attachment:", err)
        return err
    }

    // Convert the attachment content to base64
    attachmentData := aws.NewWriteAtBuffer(attachmentContent)

    // Construct the raw email content including the attachment
    rawEmail := fmt.Sprintf(`From: %s
To: %s
Subject: %s
Content-Type: multipart/mixed;
    boundary="a3f166a86b56ff6c37755292d690675717ea3cd9de81228ec2b76ed4a15d6d1a"

--a3f166a86b56ff6c37755292d690675717ea3cd9de81228ec2b76ed4a15d6d1a
Content-Type: multipart/alternative;
    boundary="sub_a3f166a86b56ff6c37755292d690675717ea3cd9de81228ec2b76ed4a15d6d1a"

--sub_a3f166a86b56ff6c37755292d690675717ea3cd9de81228ec2b76ed4a15d6d1a
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: quoted-printable

%s

--sub_a3f166a86b56ff6c37755292d690675717ea3cd9de81228ec2b76ed4a15d6d1a
Content-Type: text/html; charset=iso-8859-1
Content-Transfer-Encoding: quoted-printable

%s

--sub_a3f166a86b56ff6c37755292d690675717ea3cd9de81228ec2b76ed4a15d6d1a--

--a3f166a86b56ff6c37755292d690675717ea3cd9de81228ec2b76ed4a15d6d1a
Content-Type: text/plain; name="%s"
Content-Description: %s
Content-Disposition: attachment;filename="%s";
    creation-date="Sat, 05 Aug 2017 19:35:36 GMT";
Content-Transfer-Encoding: base64

%s

--a3f166a86b56ff6c37755292d690675717ea3cd9de81228ec2b76ed4a15d6d1a--
`, sender, recipient, subject, text, html, fileName, fileName, fileName, attachmentData.Bytes())

    // Create a new SES service client
    svc := ses.New(sess)

    // Create the input for sending raw email
    input := &ses.SendRawEmailInput{
        RawMessage: &ses.RawMessage{
            Data: []byte(rawEmail),
        },
    }

    // Send the raw email with the attachment
    _, err = svc.SendRawEmail(input)
    if err != nil {
        handleSendEmailError(err)
        return err
    }

    return nil
}

// Helper function to handle SES send email errors
func handleSendEmailError(err error) {
    if aerr, ok := err.(awserr.Error); ok {
        switch aerr.Code() {
        case ses.ErrCodeMessageRejected:
            fmt.Println(ses.ErrCodeMessageRejected, aerr.Error())
        case ses.ErrCodeMailFromDomainNotVerifiedException:
            fmt.Println(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error())
        case ses.ErrCodeConfigurationSetDoesNotExistException:
            fmt.Println(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error())
        default:
            fmt.Println(aerr.Error())
        }
    } else {
        fmt.Println(err.Error())
    }
}

Sample Usage

func GenerateTXTContent(contentArray []string) string {
    // Create a buffer to hold the contents in memory
    var buffer bytes.Buffer

    // Loop through the array and write contents to the buffer
    for _, content := range contentArray {
        buffer.WriteString(content + "\n")
    }

    // Convert the buffer to a string
    return buffer.String()
}

func GenerateTempFileContent(txtContent, fileName string) (*os.File, error) {
    // Create a temporary file in memory
    tmpFile, err := os.CreateTemp(".", fileName+"*.txt")
    if err != nil {
        fmt.Println("Error creating temporary file:", err)
        return nil, err
    }
    defer os.Remove(tmpFile.Name()) // Clean up the temporary file

    // Write the contents to the temporary file
    _, err = tmpFile.WriteString(txtContent)
    if err != nil {
        fmt.Println("Error writing to temporary file:", err)
        return nil, err
    }

    return tmpFile, nil
}

        var vouchers []string
            for i := 0; i < len(generateVoucherResponse.Item); i++ {
                vouchers = append(vouchers, generateVoucherResponse.Item[i].Code)
            }

            txtContent := GenerateTXTContent(vouchers)

            file, err := GenerateTempFileContent(txtContent, schoolDetailsRequest.Item.ID.String())
            if err != nil {
                return utils.ReturnError(w, err, http.StatusInternalServerError)
            }

            defer file.Close()
            recipient := Email

            SendRawEmailWithAttachment(consts.SenderEmail, recipient, "Registration Confirmation", `Welcome`, "Welcome", "filename.txt", cfg.AWSSESRegion, file)

Let's break down the key components of this code and understand how it achieves the task of sending emails with attachments.

1. Importing Packages

The code begins by importing necessary packages, including the AWS SDK for Go and other standard Go packages.

package oaws

import (
    "fmt"
    "io/ioutil"
    "os"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/awserr"
    "github.com/aws/aws-sdk-go/service/ses"
)

2. Function to Send Raw Email with Attachment

The SendRawEmailWithAttachment function is the core of this package. It takes various parameters such as sender, recipient, subject, HTML and plain text content, file name, AWS region, and an attachment file. It constructs a raw email with a multipart structure that includes both the email body and the attachment.

func SendRawEmailWithAttachment(sender, recipient, subject, html, text, fileName, region string, attachmentFile *os.File) error {
    // ... (code continues below)
}

3. Creating an AWS Session

The function starts by creating an AWS session using a helper function named NewSession. If an error occurs during session creation, it prints an error message and returns the error.

4. Preparing Attachment Content

The code reads the content of the attachment file and converts it to base64 format. It then constructs the raw email content by combining the email body and attachment.

5. Constructing Raw Email

The raw email is constructed as a multipart message, including both alternative text/plain and text/html content for the email body. It also includes the attachment with relevant metadata.

6. Creating SES Service Client

A new SES service client is created using the AWS session.

svc := ses.New(sess)

7. Creating Input for Sending Raw Email

The input for sending raw email is created with the raw email data.

input := &ses.SendRawEmailInput{
    RawMessage: &ses.RawMessage{
        Data: []byte(rawEmail),
    },
}

8. Sending Raw Email

Finally, the function attempts to send the raw email using the SES service client. If an error occurs, the handleSendEmailError function is called to handle specific SES-related errors.

9. Handling SES Send Email Errors

A helper function, handleSendEmailError, is defined to handle errors that may occur during the SES send email operation. It checks the error type and prints specific error messages based on the SES error code.

Conclusion

This Go package provides a convenient way to send raw emails with attachments using AWS SES. Just copy what you need, my usage would not be the same as yours. So far as you provide the right paramters to the sendEmail function, you should be good to go.