Skip to content

Bulk Mailer Example

Winni Neessen edited this page Oct 4, 2024 · 4 revisions

In this example we create a small bulk mailer for sending out the same mail to a bigger list of recipients. It is important for us to address the recipient directly in the mail, therefore we will make use of Go's html/template and text/template system together with placeholders.

It is recommended to check out the Simple Mailer Example first, as it covers some basics that will be used in this more advanced example.

Test on play.go.dev

package main

import (
	"fmt"
	ht "html/template"
	"log"
	"math/rand"
	"os"
	tt "text/template"
	"time"

	"github.com/wneessen/go-mail"
)

// User is a simple type allowing us to set a firstname, lastname and mail address
type User struct {
	Firstname string
	Lastname  string
	EmailAddr string
}

// Our sender information that will be used in the FROM address field
const (
	senderName = "ACME Inc."
	senderAddr = "noreply@acme.com"
)

const (
	textBodyTemplate = `Hi {{.Firstname}},

we are writing your to let you know that this week we have an amazing offer for you.
Using the coupon code "GOMAIL" you will get a 20% discount on all our products in our
online shop.

Check out our latest offer on https://acme.com and use your discount code today!

Your marketing team
  at ACME Inc.`

	htmlBodyTemplate = `<p>Hi {{.Firstname}},</p>
<p>we are writing your to let you know that this week we have an amazing offer for you.
Using the coupon code "<strong>GOMAIL</strong>" you will get a 20% discount on all 
our products in our online shop.</p>
<p>Check out our latest offer on <a href="https://acme.com" target="_blank">https://acme.com</a>
and use your discount code today!</p>
<p>Your marketing team<br />
&nbsp;&nbsp;at ACME Inc.</p>`
)

func main() {
	// Define a list of users we want to mail to
	userList := []User{
		{"Toni", "Tester", "toni.tester@example.com"},
		{"Tina", "Tester", "tina.tester@example.com"},
		{"John", "Doe", "john.doe@example.com"},
	}

	// Prepare the different templates
	textTpl, err := tt.New("texttpl").Parse(textBodyTemplate)
	if err != nil {
		log.Fatalf("failed to parse text template: %s", err)
	}
	htmlTpl, err := ht.New("htmltpl").Parse(htmlBodyTemplate)
	if err != nil {
		log.Fatalf("failed to parse text template: %s", err)
	}

	var messages []*mail.Msg
	random := rand.New(rand.NewSource(time.Now().UnixNano()))
	for _, user := range userList {
		randNum := random.Int31()
		message := mail.NewMsg()
		if err := message.EnvelopeFrom(fmt.Sprintf("noreply+%d@acme.com", randNum)); err != nil {
			log.Fatalf("failed to set ENVELOPE FROM address: %s", err)
		}
		if err := message.FromFormat(senderName, senderAddr); err != nil {
			log.Fatalf("failed to set formatted FROM address: %s", err)
		}
		if err := message.AddToFormat(fmt.Sprintf("%s %s", user.Firstname, user.Lastname), user.EmailAddr); err != nil {
			log.Fatalf("failed to set formatted TO address: %s", err)
		}
		message.SetMessageID()
		message.SetDate()
		message.SetBulk()
		message.Subject(fmt.Sprintf("%s, we have a great offer for you!", user.Firstname))
		if err := message.SetBodyTextTemplate(textTpl, user); err != nil {
			log.Fatalf("failed to add text template to mail body: %s", err)
		}
		if err := message.AddAlternativeHTMLTemplate(htmlTpl, user); err != nil {
			log.Fatalf("failed to add HTML template to mail body: %s", err)
		}

		messages = append(messages, message)
	}

	// Deliver the mails via SMTP
	client, err := mail.NewClient("smtp.example.com",
		mail.WithSMTPAuth(mail.SMTPAuthPlain), mail.WithTLSPortPolicy(mail.TLSMandatory),
		mail.WithUsername(os.Getenv("SMTP_USER")), mail.WithPassword(os.Getenv("SMTP_PASS")),
	)
	if err := client.DialAndSend(messages...); err != nil {
		log.Fatalf("failed to deliver mail: %s", err)
	}
	log.Printf("Bulk mailing successfully delivered.")
}

Let's take the example apart to look at some details!

At first, we define a new type for our users that we want to address. This is totally optional and is only done so we can easily work with a list of users and address them later on in our text template. How you handle this, is totally up to you and not mandatory for this to work.

In the next part we set up a simple text and HTML template mail body with placeholders that can be used with Go's html/template and text/template.

We now set up a list of users, we want to send our great bulk mailing to. We use our newly defined User type for this. With the preparation work done, we will start looping over all of our users. For each user we create a new *mail.Msg.

For bulk mailings it is common that the ENVELOPE FROM and the MAIL FROM differ, so that bounce mails are sent to some system that can mark those bounces in the local system as bounced. Therefore we set both of these from-addresses for the corresponding Msg using Msg.EnvelopeFrom and Msg.FromFormat. Msg.FromFormatallows us to specify the name and the email address seperately. It will take care of formatting them correctly in the final mail. We then finalize our message with a recipient address, make sure go-mail generates a current date and message ID as well as making sure to include the Precedence: header to Bulk, indicating that our mailing is a mass mail.

Some cool stuff happens in the follwoing lines, in which we use our prepared html/template and text/template templates and apply it to our mail message using Msg.SetBodyTextTemplate and Msg.AddAlternativeHTMLTemplate.

We provide the whole user struct as data to these methods, so that html/template and text/template can take care of replacing placeholders in the mail body. go-mail will take care of all the bells and whistles with the template handling for you. With our mail message now complete, we append it to our mail message slice.

Finally we create a new Client type and send out all of our prepared messages in one go by providing the whole slice of messages to Client.DialAndSend.

Note

You might want to use Client.DialAndSendWithContext instead, in case your application already makes use of context.Context.