Blog

How to send emails with Go: API, SMTP & Golang packages

Amy Elliott Amy Elliott
· 13 min read · Tips and resources · November 20th, 2025
Thanks to its simplicity, rich standard library, and wealth of built-in tools, there are numerous options for integrating email sending into Go applications. In this guide, we’ll cover email delivery via API, SMTP and Golang packages.

When it comes to reliable email delivery, Go really shines with its lightweight concurrency and fast performance, making it ideal for scalable systems. Plus, it’s surprisingly easy to get up and running! You can go from “What am I doing?” to sending your first email with just a few lines of code. Well, there might be a little more to it than that, but you get the gist!

And because of its popularity, you have several options to choose from depending on the complexity and needs of your project. We’ll outline some of the main ways to send emails with Go, and then give step-by-step instructions for starting with each, including code snippets.

Methods for sending emails with Go

For this guide, we’ll look at using a third-party email API or SMTP server and/or native and community Golang packages. 

Take a quick look at Go’s package library and you’ll see there are numerous options for sending emails. Some are tied to specific providers, and others allow you to use the provider of your choice. To narrow it down, we’ve included the most popular packages.

Third-party API 

Go-mail package

Native net/smtp package

Setup complexity

Moderate, requires setup with an external provider

Moderate, requires configuration of an SMTP server or an external provider

Moderate, requires configuration of an SMTP server or an external provider

Code simplicity

Simple, high-level API calls with structured requests

Clean and readable API designed for email sending

More verbose and lower-level; you handle most details yourself

Features

Often includes advanced features such as templates, analytics, and inbound routing

Supports attachments, HTML, multiple recipients, and headers

Basic sending capability with optional authentication

Dependencies

External API service and Go HTTP client or SDK

Go package dependency and SMTP server

SMTP server required (third-party or self-hosted) but no extra packages needed

Authentication

API keys or OAuth (depends on provider)

SMTP authentication (PLAIN, LOGIN, etc.)

Manual SMTP authentication setup

Scalability

High, provider handles throughput

Depends on your SMTP server’s configuration

Limited, concurrent connections must be managed manually

Error handling

Structured responses, error codes and handling from provider

Built-in error handling for most send issues

Manual error checking required

Deliverability

Managed by provider; often optimized for inbox placement

Depends on SMTP server setup and reputation

Depends on SMTP server setup and reputation

Cost

Paid subscription or cost per email

Free/open source, but SMTP costs may apply

Free/open source, but SMTP costs may apply

Security

TLS/SSL handled by provider

Supports STARTTLS and SSL connections

STARTTLS support available

Best for

Apps that need reliable delivery and advanced features

General-purpose applications or self-hosted email sending

Lightweight tools, prototypes, or educational purposes

Send emails in Go with an API

Let’s get started with sending via an API. As you can see in the table above, a third-party API offers a few advantages, such as additional features out of the box (like templates, analytics, domain management, etc.) and managed deliverability (so you won’t need to worry about your emails hitting inboxes). 

This makes an API a solid choice for high-volume senders, apps or services for which reliable delivery is crucial, and agencies or multi-brand organizations that manage the emails for multiple senders or domains. 

So let’s run through how you can send your first email in Go via an API— we’ll use MailerSend’s email API for this example.

Getting started

If you don’t already have a MailerSend account, you can create one for free. You’ll get a trial domain to play around with, but if you prefer, you can also add and verify your own domain.

Next, create an API key to use in your project. To do this, head to Integrations in the left-hand menu, then click Manage in the API tokens section.

Click Create new token and enter the details. Choose the domain you want to create the token for, select an expiration date, and allow the appropriate access. Then click Generate token.

A GIF showing how to create an API token in MailerSend.

Copy and save or download the token. After you close this page, you won’t be able to view the token again for security reasons.

Check out our help guide to learn more about creating and managing API tokens.

1. Install the MailerSend Go SDK with the following command:

go get github.com/mailersend/mailersend-go

2. Set your API key as an environment variable or add it to a .env file:

Bash:

export MAILERSEND_API_KEY="your_api_key"

Powershell:

$env:MAILERSEND_API_KEY="your_api_key"

Send an HTML email

Add the following code to your main.go file. This will send a basic HTML email; just remember to replace the sender and recipient details with your own. 

package main

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/mailersend/mailersend-go"
)

func main() {
	// Create an instance of the mailersend client
	ms := mailersend.NewMailersend(os.Getenv("MAILERSEND_API_KEY"))

	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	subject := "Subject"
	text := "This is the text content"
	html := "<p>This is the HTML content</p>"

	from := mailersend.From{
		Name:  "From name",
		Email: "from@email.com",
	}

	recipients := []mailersend.Recipient{
		{
			Name:  "Recipient name",
			Email: "recipient@example.com",
		},
	}

	message := ms.Email.NewMessage()

	message.SetFrom(from)
	message.SetRecipients(recipients)
	message.SetSubject(subject)
	message.SetHTML(html)
	message.SetText(text)

	// Send the email with error handling
	res, err := ms.Email.Send(ctx, message)
	if err != nil {
		fmt.Printf("❌ Failed to send email: %v\n", err)
		return
	}

	// Success message with message ID
	fmt.Printf("✅ Email sent successfully! Message ID: %s\n", res.Header.Get("X-Message-Id"))
}

And run the program to send the email:

go run main.go

Send a personalized, templated email

One of the main benefits of using an API is that you can send templated emails, which can be reused, personalized, and designed really easily using one of MailerSend’s template builders. 

To send a templated email, we’ll tweak the code a little to include the template ID, plus any of the values needed to populate personalization variables. This example uses the variable {{name}} in the template.

package main

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/mailersend/mailersend-go"
)

func main() {
	// Create an instance of the mailersend client
	ms := mailersend.NewMailersend(os.Getenv("MAILERSEND_API_KEY"))

	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	subject := "Subject"

	from := mailersend.From{
		Name:  "From name",
		Email: "from@email.com",
	}

	recipients := []mailersend.Recipient{
		{
			Name:  "Recipient name",
			Email: "recipient@example.com",
		},
	}

	personalization := []mailersend.Personalization{
		{
			Email: "recipient@example.com",
			Data: map[string]interface{}{
				"name": "Recipient name",
			},
		},
	}

	message := ms.Email.NewMessage()

	message.SetFrom(from)
	message.SetRecipients(recipients)
	message.SetSubject(subject)
	message.SetTemplateID("template_ID")
	message.SetPersonalization(personalization)

	// Send the email with error handling
	res, err := ms.Email.Send(ctx, message)
	if err != nil {
		fmt.Printf("❌ Failed to send email: %v\n", err)
		return
	}

	// Success message with message ID
	fmt.Printf("✅ Email sent successfully! Message ID: %s\n", res.Header.Get("X-Message-Id"))
}

Send an email with an attachment

To send an email with an attachment, we’ll add a snippet of code to our HTML email to open and encode the file, create the attachment, and add it to the message. Here’s the snippet:

// Open the file
f, err := os.Open("./file.jpg")
if err != nil {
panic(fmt.Errorf("failed to open file: %w", err))
}
defer f.Close()

// Read all bytes
contentBytes, err := io.ReadAll(f)
if err != nil {
panic(fmt.Errorf("failed to read file: %w", err))
}

// Base64 encode
encoded := base64.StdEncoding.EncodeToString(contentBytes)

// Create the attachment
attachment := mailersend.Attachment{
Filename:    "file.jpg",
Content:     encoded,
Disposition: "attachment",
}

// Add to the message
message.AddAttachment(attachment)

And here it is added to our HTML email:

package main

import (
	"context"
	"encoding/base64"
	"fmt"
	"io"
	"os"
	"time"

	"github.com/mailersend/mailersend-go"
)

func main() {
	// Create an instance of the mailersend client
	ms := mailersend.NewMailersend(os.Getenv("MAILERSEND_API_KEY"))

	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	subject := "Test email with attachment"
	text := "This is the text content"
	html := "<p>This is the HTML content</p>"

	from := mailersend.From{
		Name:  "From name",
		Email: "from@email.com",
	}

	recipients := []mailersend.Recipient{
		{
			Name:  "Recipient name",
			Email: "recipient@example.com",
		},
	}

	tags := []string{"foo", "bar"}

	message := ms.Email.NewMessage()

	message.SetFrom(from)
	message.SetRecipients(recipients)
	message.SetSubject(subject)
	message.SetHTML(html)
	message.SetText(text)
	message.SetTags(tags)

	// Open the file
	f, err := os.Open("./file.jpg")
	if err != nil {
		panic(fmt.Errorf("failed to open file: %w", err))
	}
	defer f.Close()

	// Read all bytes
	contentBytes, err := io.ReadAll(f)
	if err != nil {
		panic(fmt.Errorf("failed to read file: %w", err))
	}

	// Base64 encode
	encoded := base64.StdEncoding.EncodeToString(contentBytes)

	// Create the attachment
	attachment := mailersend.Attachment{
		Filename:    "file.jpg",
		Content:     encoded,
		Disposition: "attachment",
	}

	// Add to the message
	message.AddAttachment(attachment)

	// Send the email with error handling
	res, err := ms.Email.Send(ctx, message)
	if err != nil {
		fmt.Printf("❌ Failed to send email: %v\n", err)
		return
	}

	// Success message with message ID
	fmt.Printf("✅ Email sent successfully! Message ID: %s\n", res.Header.Get("X-Message-Id"))
}

Send an email with an embedded image/inline attachment

If you want to embed an image instead of simply attaching it, we can create the attachment, set the Disposition to inline, and set an ID that we can use in our HTML to embed it. 

// Inline image attachment
contentBytes, err := os.ReadFile("./image.jpg") // read file in one line
if err != nil {
	panic(err)
}
encoded := base64.StdEncoding.EncodeToString(contentBytes)

message.AddAttachment(mailersend.Attachment{
	Filename:    "image.jpg",
	Content:     encoded,
	Disposition: mailersend.DispositionInline,
	ID:          "image123", // matches cid in HTML
})

Here’s the working code for the embedded image in our HTML email:

package main

import (
	"context"
	"encoding/base64"
	"fmt"
	"os"
	"time"

	"github.com/mailersend/mailersend-go"
)

func main() {
	// Create an instance of the mailersend client
	ms := mailersend.NewMailersend(os.Getenv("MAILERSEND_API_KEY"))

	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	subject := "Test email with attachment"
	text := "This is the text content"
	html := `<p>This is the HTML content</p>
	<p>Here is an inline image:</p>
	<img src="cid:image123" alt="My Image"/>`

	from := mailersend.From{
		Name:  "From name",
		Email: "from@email.com",
	}

	recipients := []mailersend.Recipient{
		{
			Name:  "Recipient name",
			Email: "recipient@example.com",
		},
	}

	tags := []string{"foo", "bar"}

	message := ms.Email.NewMessage()

	message.SetFrom(from)
	message.SetRecipients(recipients)
	message.SetSubject(subject)
	message.SetHTML(html)
	message.SetText(text)
	message.SetTags(tags)

	// Inline image attachment
	contentBytes, err := os.ReadFile("./image.jpg") // read file in one line
	if err != nil {
		panic(err)
	}
	encoded := base64.StdEncoding.EncodeToString(contentBytes)

	message.AddAttachment(mailersend.Attachment{
		Filename:    "image.jpg",
		Content:     encoded,
		Disposition: mailersend.DispositionInline,
		ID:          "image123", // matches cid in HTML
	})

	// Send the email with error handling
	res, err := ms.Email.Send(ctx, message)
	if err != nil {
		fmt.Printf("❌ Failed to send email: %v\n", err)
		return
	}

	// Success message with message ID
	fmt.Printf("✅ Email sent successfully! Message ID: %s\n", res.Header.Get("X-Message-Id"))
}

Send bulk email

The MailerSend API has a bulk email endpoint, which makes it possible to bypass rate limiting and optimize sendings to preserve your API quota. This lets you send multiple personalized messages with a single API call. Since we’re sending with the bulk endpoint, we don’t need to use goroutines here, as it won’t impact our API call. 

In the example below, we loop through the recipients and assign each one a message so that each person gets an individual email, instead of all recipients getting the same email.

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/mailersend/mailersend-go"
)

func main() {
	apiKey := os.Getenv("MAILERSEND_API_KEY")
	if apiKey == "" {
		log.Fatal("MAILERSEND_API_KEY is not set")
	}

	// Create client
	ms := mailersend.NewMailersend(apiKey)

	// Safeguard timeout
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	from := mailersend.From{
		Name:  "From name",
		Email: "from@email.com",
	}

	// Example: multiple recipients, each receives an individual email
	recipients := []mailersend.Recipient{
		{Name: "Amy", Email: "amy@example.com"},
		{Name: "Bob", Email: "bob@example.com"},
		{Name: "Lisa", Email: "lisa@example.com"},
	}

	var messages []*mailersend.Message

	for i, r := range recipients {
		msg := &mailersend.Message{
			From: from,
			Recipients: []mailersend.Recipient{
				// each email has *only one* recipient -> no one sees each other
				r,
			},
			Subject: fmt.Sprintf("Hello %s! Here is your subject #%d", r.Name, i+1),
			Text:    fmt.Sprintf("Hi %s,\nThis is your text version email #%d.", r.Name, i+1),
			HTML:    fmt.Sprintf("<p>Hi %s,</p><p>This is your HTML email <strong>#%d</strong>.</p>", r.Name, i+1),
		}

		// Basic validation
		if r.Email == "" {
			log.Printf("Skipping empty email at index %d", i)
			continue
		}

		messages = append(messages, msg)
	}

	if len(messages) == 0 {
		log.Fatal("No valid messages to send")
	}

	// Send bulk
	resp, body, err := ms.BulkEmail.Send(ctx, messages)
	if err != nil {
		log.Printf("❌ Bulk send failed: %v", err)
		log.Printf("Response: %v", resp)
		log.Printf("Body: %s", body)
		return
	}

	log.Println("✅ Bulk email successfully queued!")
	log.Printf("Response: %v", resp)
	log.Printf("Body: %s", body)
}

Send emails in Go with SMTP and net/smtp

Net/smtp is Go’s standard package for sending emails. It offers basic sending and lacks features such as attachments, HTML emails, and the ability to scale easily, making it more suited for basic, low-volume sending of text-based emails.

Getting started

You’ll need to use an SMTP server to send emails using this method, so we’ll use MailerSend again, this time using SMTP credentials. 

If you don’t already have a MailerSend account, you can create one for free. You’ll get a trial domain to play around with, but if you prefer, you can add and verify your own domain.

Next, you’ll need to generate an SMTP user and credentials. To do this, go to Email, Domains and click Manage for the domain you want to use. Scroll to the SMTP section, and click Generate new user.

Enter a name for the SMTP user and click Save user. You can now use this username and password to authenticate when you send an email.

Check out our help guide to learn more about using MailerSend’s SMTP relay

Send a plain-text email

Net/smtp is native to Go, so you won’t need to install any packages. In our example, however, we’re using environment variables set in a .env file, so we’ve installed the godotenv package. Then you can use the following code in your main.go file to send your email. Note, we’ve also set our SMTP host and port in the .env file.

package main

import (
	"fmt"
	"log"
	"net/smtp"
	"os"

	"github.com/joho/godotenv"
)

func main() {
	// Load .env file
	err := godotenv.Load()
	if err != nil {
		log.Println("Warning: .env file not found, relying on system env variables")
	}

	// Load environment variables
	smtpUsername := os.Getenv("SMTP_USERNAME")
	smtpPassword := os.Getenv("SMTP_PASSWORD")
	smtpHost := os.Getenv("SMTP_HOST")
	smtpPort := os.Getenv("SMTP_PORT")

	if smtpUsername == "" || smtpPassword == "" {
		log.Fatal("SMTP_USERNAME or SMTP_PASSWORD not set in environment variables")
	}

	// MailerSend SMTP authentication
	auth := smtp.PlainAuth("", smtpUsername, smtpPassword, smtpHost)

	from := "from@email.com"
	to := []string{"recipient@example.com"}

	// Email message (RFC 822 format)
	msg := []byte("To: recipient@example.com\r\n" +
		"Subject: Test email via MailerSend SMTP Relay\r\n" +
		"\r\n" +
		"Hello! This is a test email sent using Go and net/smtp.\r\n")

	// SMTP server address
	addr := fmt.Sprintf("%s:%s", smtpHost, smtpPort)

	// Send email
	err = smtp.SendMail(addr, auth, from, to, msg)
	if err != nil {
		log.Fatal("Error sending email: ", err)
	}

	fmt.Println("Email sent successfully!")
}

You can also use basic HTML for styling emails sent with net/smtp. You just need to add the correct MIME headers and HTML to the msg variable:

// HTML email with proper MIME headers
	msg := []byte(
		"To: recipient@example.com\r\n" +
			"Subject: HTML Email via MailerSend SMTP Relay\r\n" +
			"MIME-Version: 1.0\r\n" +
			"Content-Type: text/html; charset=\"UTF-8\"\r\n" +
			"\r\n" +
			"<h1 style='color:#4A90E2;'>Hello!</h1>" +
			"<p>This is a <strong>HTML email</strong> sent using <code>net/smtp</code> 🎉</p>" +
			"<p>You can include <em>styles, colors, links, etc.</em></p>" +
			"<a href='https://mailersend.com'>Visit MailerSend</a>",
	)

Send emails in Go using SMTP and Go-mail

Go-mail is a community package built upon the standard Go library and extended packages, and provides access to additional features like attachments, headers, and more. There’s a lot more functionality available than with net/smtp, plus it’s just as easy to use.

Since go-mail is a more flexible solution than net/smtp, it's a solid solution if you run your own mail server or third-party services are not allowed, want a simpler SMTP-based solution instead of API, need a solution for internal emails, or you need simple email delivery for lightweight services or CLIs.

Getting started

Here we’re using MailerSend again as our SMTP server. If you don’t already have a MailerSend account, you can create one for free. You’ll get a trial domain to play around with, but if you prefer, you can add and verify your own domain.

To generate an SMTP user and credentials, go to Email, Domains and click Manage for the domain you want to use. Scroll to the SMTP section, and click Generate new user.

Enter a name for the SMTP user and click Save user. You can now use this username and password to authenticate when you send an email.

Check out our help guide to learn more about using MailerSend’s SMTP relay

In your Go project, install the go-mail package:

go get github.com/wneessen/go-mail

Send a plain-text or HTML email

To send a plain-text email, use the following code. We’re also using the godotenv package for our environment variables.

package main

import (
	"log"
	"os"

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

func main() {
	// Load credentials from environment variables
	err := godotenv.Load()
	if err != nil {
		log.Println("Warning: .env file not found, relying on system env variables")
	}
	smtpUser := os.Getenv("SMTP_USERNAME")
	smtpPass := os.Getenv("SMTP_PASSWORD")

	if smtpUser == "" || smtpPass == "" {
		log.Fatal("SMTP_USERNAME or SMTP_PASSWORD is not set in environment")
	}

	msg := mail.NewMsg()

	// Set From and To
	if err := msg.From("from@email.com"); err != nil {
		log.Fatalf("Invalid From address: %v", err)
	}
	if err := msg.To("recipient@example.com"); err != nil {
		log.Fatalf("Invalid To address: %v", err)
	}

	msg.Subject("Hello from go-mail + MailerSend")
	msg.SetBodyString(mail.TypeTextPlain, "Hey! This is a test email sent using go-mail and MailerSend.")

	// Create SMTP client with env credentials
	client, err := mail.NewClient(
		"smtp.mailersend.net",
		mail.WithPort(587),
		mail.WithTLSPolicy(mail.TLSMandatory),
		mail.WithSMTPAuth(mail.SMTPAuthPlain),
		mail.WithUsername(smtpUser),
		mail.WithPassword(smtpPass),
	)
	if err != nil {
		log.Fatalf("Failed to create mail client: %v", err)
	}
	defer client.Close()

	// Send
	if err := client.DialAndSend(msg); err != nil {
		log.Fatalf("Failed to send message: %v", err)
	}

	log.Println("Email sent successfully!")
}

To use HTML in your email, you can just add another body to the same message and specify the MIME type as HTML. 

msg.Subject("Hello from go-mail + MailerSend")
	msg.SetBodyString(mail.TypeTextPlain, "Hey! This is a test email sent using go-mail and MailerSend.")
    	msg.SetBodyString(mail.TypeTextHTML, "<h1>Hey!</h1><p>This is a test email sent using <strong>go-mail and MailerSend</strong>.</p>")

Send an email with an attachment

Attaching a file to an email is easy—just add AttachFile along with your file path to the message. In this example, we’re attaching a file from the disk, but you can also attach via io.Reader.

package main

import (
    "log"
    "os"

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

func main() {
    // Load credentials from environment variables
    if err := godotenv.Load(); err != nil {
        log.Println("Warning: .env file not found, relying on system env variables")
    }

    smtpUser := os.Getenv("SMTP_USERNAME")
    smtpPass := os.Getenv("SMTP_PASSWORD")

    if smtpUser == "" || smtpPass == "" {
        log.Fatal("SMTP_USERNAME or SMTP_PASSWORD is not set in environment")
    }

    msg := mail.NewMsg()

    // Set From and To
    if err := msg.From("from@email.com"); err != nil {
        log.Fatalf("Invalid From address: %v", err)
    }
    if err := msg.To("recipient@example.com"); err != nil {
        log.Fatalf("Invalid To address: %v", err)
    }

    // Set subject and body
    msg.Subject("Hello from go-mail + MailerSend")
    msg.SetBodyString(mail.TypeTextPlain, "Hey! This is a test email with an attachment sent using go-mail and MailerSend.")
    msg.SetBodyString(mail.TypeTextHTML, "<h1>Hey!</h1><p>This is a test email with an attachment sent using <strong>go-mail and MailerSend</strong>.</p>")

    // ----------------------------
    // ADD ATTACHMENT
    // ----------------------------
    // Example: attach "document.pdf" from current directory
    msg.AttachFile("image.jpg")

    // Create SMTP client with env credentials
    client, err := mail.NewClient(
        "smtp.mailersend.net",
        mail.WithPort(587),
        mail.WithTLSPolicy(mail.TLSMandatory),
        mail.WithSMTPAuth(mail.SMTPAuthPlain),
        mail.WithUsername(smtpUser),
        mail.WithPassword(smtpPass),
    )
    if err != nil {
        log.Fatalf("Failed to create mail client: %v", err)
    }
    defer client.Close()

    // Send
    if err := client.DialAndSend(msg); err != nil {
        log.Fatalf("Failed to send message: %v", err)
    }

    log.Println("Email sent successfully with attachment!")
}

Send an email with an embedded image

Rather than using AttachFile to embed a file, go-mail has a separate EmbedFile function. You can simply add the following snippet to your message, and then reference the Content ID in your HTML.

// Embed image and set email body to reference the embedded image
    msg.EmbedFile(
        "image.jpg",
        mail.WithFileContentID("image123"),    // Set the Content‑ID for CID reference
        mail.WithFileName("image.jpg"),    // Optional: define the filename for the embedded file
)

HTML:

msg.SetBodyString(mail.TypeTextHTML, `<h1>Hey!</h1><p>This is a test email with an embedded image sent using <strong>go-mail and MailerSend</strong>.</p><img src="cid:image123" alt="Embedded Image">`)

Send an email to multiple recipients (bulk email)

To send an email to multiple recipients, so that each one gets their own message, we’ll loop over our list of recipients. This involves creating a new message for each recipient, setting the to address for each one, and calling DialAndSend for each one. 

We can speed up the process by sending the emails concurrently, using goroutines and a worker pool to prevent overloading the SMTP server by setting a maximum of 5 emails to be processed at a time. We’ve also configured it so that each worker creates its own SMTP client, since SMTP clients cannot be shared.

package main

import (
    "log"
    "os"
    "sync"

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

func main() {
    // Load credentials from environment variables
    if err := godotenv.Load(); err != nil {
        log.Println("Warning: .env file not found, relying on system env variables")
    }

    smtpUser := os.Getenv("SMTP_USERNAME")
    smtpPass := os.Getenv("SMTP_PASSWORD")

    if smtpUser == "" || smtpPass == "" {
        log.Fatal("SMTP_USERNAME or SMTP_PASSWORD is not set in environment")
    }

    // List of recipients
    recipients := []string{
        "recipient1@example.com",
        "recipient2@example.com",
        "recipient3@example.com",
    }

    // -------------------------------------------------------------------
    // WORKER POOL SETTINGS
    // -------------------------------------------------------------------
    workerCount := 5 // how many emails to process in parallel

    recipientCh := make(chan string)
    var wg sync.WaitGroup

    // Start worker pool
    for i := 0; i < workerCount; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()

            for r := range recipientCh {
                // Each worker creates its own SMTP client
                client, err := mail.NewClient(
                    "smtp.mailersend.net",
                    mail.WithPort(587),
                    mail.WithTLSPolicy(mail.TLSMandatory),
                    mail.WithSMTPAuth(mail.SMTPAuthPlain),
                    mail.WithUsername(smtpUser),
                    mail.WithPassword(smtpPass),
                )
                if err != nil {
                    log.Printf("Failed to create mail client for %s: %v", r, err)
                    continue
                }

                msg := mail.NewMsg()

                if err := msg.From("from@email.com"); err != nil {
                    log.Printf("Invalid From address for %s: %v", r, err)
                    continue
                }

                if err := msg.To(r); err != nil {
                    log.Printf("Invalid recipient %s: %v", r, err)
                    continue
                }

                msg.Subject("Hello from go-mail + MailerSend")
                msg.SetBodyString(mail.TypeTextPlain,
                    "Hey! This is a test email sent using go-mail and MailerSend.")
                msg.SetBodyString(mail.TypeTextHTML,
                    "<h1>Hey!</h1><p>This is a test email sent using <strong>go-mail and MailerSend</strong>.</p>")

                // Send
                if err := client.DialAndSend(msg); err != nil {
                    log.Printf("Failed to send to %s: %v", r, err)
                } else {
                    log.Printf("Email sent to %s", r)
                }

                client.Close()
            }
        }()
    }

    // Send jobs to the workers
    for _, r := range recipients {
        recipientCh <- r
    }

    close(recipientCh)
    wg.Wait()

    log.Println("All emails processed.")
}

Tips for troubleshooting

At the time of writing, the code examples in this guide have been tested and work as intended. But if your emails aren’t sending, here’s a checklist to help you get to the bottom of what could be causing them to fail. 

1. Double-check your authentication

This might seem like a no-brainer, but it’s easy to add the incorrect SMTP credentials or API key, or create an API key with the wrong permissions. Heck, I forgot to set my environment variables at least once while I was testing for this article. 

So the first thing you want to do is double-check that:

a) Your SMTP credentials or API key is correct

b) They have the correct permissions/are assigned to the correct domain

c) Your environment variables are loading correctly (never hard-code them)

2. Make sure you're using the correct SMTP server and port

For MailerSend, you should be using smtp.mailersend.net and port 587. You can also use port 2525 if necessary. 

3. Check that you’re using the correct endpoints and methods

If you’re using an API package, check the documentation to make sure you have the right endpoint and method. For MailerSend, you can check our API Hub, GitHub, and API reference.

4. Confirm your domain verification records

Incorrectly configured SPF and DKIM records can lead to deliverability issues. Check that they are configured correctly by using an SPF and DKIM record check tool like MXToolBox. In MailerSend, you’ll be able to see if your domain is verified on the domains page. 

5. Know your rate limits

If you’re sending high volumes of emails and your setup isn’t optimized for it, you could quickly hit your API or SMTP service’s rate limit. Check out the rate limiting imposed by your service, and use bulk emailing to send multiple emails in a single request. With the MailerSend API, you can send up to 500 emails in a single request, each with up to 50 recipients. 

Learn about MailerSend's daily request quota.

6. Use AI to help with troubleshooting

If you’re using MailerSend, you can connect our MCP server to your AI coding environment or tool (VSCode, Claude, Cursor, ChatGPT or Gemini CLI). This will connect your account so that you can perform actions, make API calls, and access data with natural language prompts. 

When it comes to troubleshooting, you can use the MCP to analyze activity and check your implementation for potential issues. For example, you could:

  • Ensure your domain has the proper security settings

  • Verify your SPF/DKIM records

  • Get a list of all emails that have failed for a specific time period

  • Analyze bounce patterns

To get started, check out MCP help guide, MCP developer guide, and MCP use cases.

FAQs

1. Which Go package should I use for sending email?

There is no one-size-fits-all approach for sending emails with Go. It depends on your sending volume, purpose for sending, features you’ll need, current setup, and limitations. 

For very basic, low-volume text-only emails, testing or internal needs, net/smtp could be a simple but effective way to send emails. If you need a bit more scalability and extra features, but still want to use your own mail server, go-mail offers extra flexibility and functionality for more advanced email sending with features for good deliverability. But, if you want built-in features for activity tracking, advanced communication systems, email templating, and very high-volume sending, an email API would be the better fit. 

2. How do I add file attachments to an email using Go?

How you attach files will depend on the package you are using. Net/smtp does not support attachments, but MailerSend and go-mail do.

3. How can my Go application know if an email was successfully delivered or bounced?

Net/smtp will only tell you if the server accepted the message. To know if the message was delivered or failed, you’ll need inbox monitoring or bounce parsing. Go-mail is similar in that it doesn’t include bounce tracking or offer webhooks, so you’d need to implement it yourself or set Return-Path to a monitored inbox. An API like MailerSend includes delivery tracking, so you can use webhooks to receive real-time notifications about whether the email has been delivered or bounced. 

4. What are the sending limitations of using a personal email account (like Gmail) for an application?

For Gmail, there is a daily sending limit of 500 emails for personal accounts, and a limit of 100 recipients per email. If you exceed these limits, your account could be temporarily blocked or flagged for spam. 

5. How do I use dynamic, pre-designed templates from MailerSend in my Go code?

Simply set the template ID for the message: message.SetTemplateID("template_ID") and ensure you’ve configured your personalization variables.

Get started with MailerSend for free

Sign up and start sending in minutes with our SDKs, developer documentation, and free testing domain.

Amy Elliott
I’m Amy, Content Writer at MailerSend. As a child, I dreamt about writing a book and practiced by tearing pages from an A4 notepad and binding them with sugar paper. The book is pending but in the meantime, I love taking a deep dive into technical topics and sharing insights on email metrics and deliverability.