Developer’s Guide on Sending HTML Emails in Go: SMTP and Email API Covered

On December 13, 2024
11min read
Ivan Djuric, an author at Mailtrap
Ivan Djuric Technical Content Writer @Mailtrap

In this step-by-step article, I’ll show you how to send HTML emails in Go using both an SMTP server and an email API.

As the sending solution, I’ve used Mailtrap SMTP/API for its reliability and ease-of-use. On top of that, I’ll also cover email testing and make sure our HTML emails look just the way we intend them to.

The code snippets I provide are compatible with Go 1.13 and above.

Send HTML email in Go using SMTP

To start sending HTML emails from your Go project via SMTP, follow these steps:

1. Install Gomail

As Go can’t leverage an SMTP server on its own, I suggest using Gomail, a community-driven package that’s super simple to use.

To install it, run the following command in your preferred code editor:

go get gopkg.in/mail.v2

2. Integrate Mailtrap SMTP with your app

Next, let’s obtain our SMTP server credentials and integrate them with our app.

Start by creating a free Mailtrap account, logging in, and navigating to Sending Domains, where you’ll need to add and verify your domain. This takes only ~5 minutes, and you can use our detailed Knowledge Base article as your step-by-step guide.

Then, add the DNS records provided by Mailtrap to your domain provider’s DNS. By doing this, you verify the domain ownership.

3. Configure Gomail

With your domain verified, all there’s left to do is copy/paste the following code into your main configuration file (e.g., main.go):

package main

import (
    "fmt"
    gomail "gopkg.in/mail.v2"
)

func main() {
    // Create a new message
    message := gomail.NewMessage()

    // Set email headers
    message.SetHeader("From", "youremail@email.com")
    message.SetHeader("To", "recipient1@email.com")
    message.SetHeader("Subject", "Hello from the Mailtrap team")

    // Set email body to HTML format
    message.SetBody("text/html", `
        <html>
            <body>
                <h1>This is a Test Email</h1>
                <p><b>Hello!</b> This is a test email with HTML formatting.</p>
                <p>Thanks,<br>Mailtrap</p>
            </body>
        </html>
    `)

    // Set up the SMTP dialer
    dialer := gomail.NewDialer("live.smtp.mailtrap.io", 587, "api", "1a2b3c4d5e6f7g")

    // Send the email
    if err := dialer.DialAndSend(message); err != nil {
        fmt.Println("Error:", err)
        panic(err)
    } else {
        fmt.Println("HTML Email sent successfully!")
    }
}

To run the script, execute the following command: go run.main.go.

Code explanation:

  • The script is designed to send basic HTML emails. However, I’ll also cover dynamic content and email templates a bit later in the article.
  • The key function is SetBody, which is set to “text/html” in this case, so it defines the content of the email body as HTML.
    • Make sure to write your HTML code within the string passed to SetBody and enclose it in backticks (`) for better readability.
  • If you want to send plain text along with your HTML emails in case some of your recipients’ clients don’t support HTML, use the AddAlternative function like so:
m.SetBody( "text/html", "<p>Hello!</p>")

m.AddAlternative("text/plain", "Hello!")

Send HTML email with attachment

To add attachments, all you have to do is move the desired image/document to your project folder and specify its name under the Attach method. 

For example, if you want to add an invoice to your email, simply copy the invoice#1.pdf and specify it in the code, just like this:

package main

import (
    "fmt"
    gomail "gopkg.in/mail.v2"
)

func main() {
    // Create a new message
    message := gomail.NewMessage()

    // Set email headers
    message.SetHeader("From", "youremail@email.com")
    message.SetHeader("To", "abc@gmail.com")
    message.SetHeader("Subject", "Test Email with Attachment")

    // Set email body
    message.SetBody("text/html", `
        <html>
            <body>
                <h1>This is a Test Email</h1>
                <p><b>Hello!</b> Please find the attachment below.</p>
                <p>Thanks,<br>Mailtrap</p>
            </body>
        </html>
    `)

    // Add attachments
    message.Attach("/invoice#1.pdf")

    // Set up the SMTP dialer
    dialer := gomail.NewDialer("live.smtp.mailtrap.io", 587, "api", "1a2b3c4d5e6f7g")

    // Send the email
    if err := dialer.DialAndSend(message); err != nil {
        fmt.Println("Error:", err)
        panic(err)
    } else {
        fmt.Println("Email sent successfully with attachments!")
    }
}

Send HTML email with embedded image

To add an embedded image to your HTML emails and display it inline rather than as an attachment, use the conveniently named Embed() method.

Here’s how it works in action:

package main

import (
    "fmt"
    gomail "gopkg.in/mail.v2"
)

func main() {
    // Create a new message
    message := gomail.NewMessage()

    // Set email headers
    message.SetHeader("From", "youremail@email.com")
    message.SetHeader("To", "abc@gmail.com")
    message.SetHeader("Subject", "Test Email with Embedded Image")

    // Embed image and set email body to reference the embedded image
    message.Embed("/path/to/image.jpg")

    message.SetBody("text/html", `
        <html>
            <body>
                <h1>Test Email with Embedded Image</h1>
                <p><b>Hello!</b> This email contains an embedded image:</p>
                <img src="cid:image.jpg" alt="Embedded Image">
                <p>Thanks,<br>Mailtrap</p>
            </body>
        </html>
    `)

    // Set up the SMTP dialer
    dialer := gomail.NewDialer("live.smtp.mailtrap.io", 587, "api", "1a2b3c4d5e6f7g")

    // Send the email
    if err := dialer.DialAndSend(message); err != nil {
        fmt.Println("Error:", err)
        panic(err)
    } else {
        fmt.Println("Email sent successfully with an embedded image!")
    }
}

Sending HTML email with dynamic content

To send emails with dynamic content, you can use Go’s html/template package, which allows you to insert variables into your HTML.

Now, for example, let’s say you have a recipient called Alice, and you want to send her a dynamic email with the link to her invoice. Here’s the code you could use:

package main

import (
    "html/template"
    "os"
)

type User struct {
    Name string
    Link string
}

func main() {
    tmpl := `<html>
                <body>
                    <h1>Hello, {{.Name}}!</h1>
                    <p>Your shopping invoice is available <a href="{{.Link}}">here</a>.</p>
                </body>
             </html>`

    t, err := template.New("webpage").Parse(tmpl)
    if err != nil {
        panic(err)
    }

    user := User{Name: "Alice", Link: "https://example.com/invoice/alice"}
    t.Execute(os.Stdout, user)
}

Code breakdown:

  • func main() now includes tmpl variable with the dynamic HTML template as a string wrapped in tags.
  • The User struct holds data such as recipient’s name and the link.
  • t.Execute renders the template and substitutes {{.Name}} and {{.Link}} with Alice’s name and her invoice URL, then outputs the HTML to os.Stdout.

How to style HTML email template

To spice it up a bit, you can use inline CSS directly within your HTML template in Go. Besides being easy to use, inline styles come with the benefit of working well with most email clients.

Here’s how it works:

1. Create an email template

In your project directory, create a new file and call it email.template. Then, paste the following code into the same file:

const emailTemplate = `
<html>
    <body style="font-family: Arial, sans-serif; color: #333; background-color: #f9f9f9; padding: 20px;">
        <div style="max-width: 600px; margin: auto; background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">
            <h1 style="color: #4CAF50;">Hello, {{.Name}}!</h1>
            <p style="font-size: 16px; line-height: 1.5;">This is a test email with styled HTML content.</p>
            <p style="font-size: 14px; color: #666;">Thank you for using our service. We hope you enjoy this email layout!</p>
            <a href="{{.Link}}" style="display: inline-block; margin-top: 20px; padding: 10px 15px; color: #fff; background-color: #4CAF50; text-decoration: none; border-radius: 5px;">Learn More</a>
        </div>
    </body>
</html>
`

2. Render the template 

To execute the template, we’ll use html/template, and again, we’ll dynamically fill in the values for Name and Link. For example:

import (
    "bytes"
    "html/template"
    "fmt"
    gomail "gopkg.in/mail.v2"
)

func sendStyledEmail(name, link string) {
    // Parse the template
    tmpl, err := template.New("email").Parse(emailTemplate)
    if err != nil {
        fmt.Println("Error parsing template:", err)
        return
    }

    // Execute the template with dynamic data
    var body bytes.Buffer
    err = tmpl.Execute(&body, struct {
        Name string
        Link string
    }{Name: name, Link: link})

    if err != nil {
        fmt.Println(“error executing template:”, err)
        return
    }

    // Create a new message
    message := gomail.NewMessage()
    message.SetHeader("From", "youremail@gmail.com")
    message.SetHeader("To", "recipient@example.com")
    message.SetHeader("Subject", "Styled HTML Email")
    message.SetBody("text/html", body.String())

    // Send email (setup dialer as shown before)
    // ...
}

func main() {
    sendStyledEmail(“Your name”, “https://example.com”)
}

Keep in mind that you can replace the Name and Link variables and add new ones if you want, such as OrderNumber, ProductName, Email, EventDate, EventLocation, etc.

You can also use Mailtrap’s drag-and-drop email builder, a part of the Email Templates feature, which allows you to design, edit, and host HTML email templates on the platform.

I must also note that the drag-and-drop email builder comes free with any of the plans you choose, no additional costs included!

Send HTML email in Go using email API

Did you know that you can automate the HTML sending process in Go with Mailtrap’s very own email API? The API offers reliable email infrastructure with high deliverability rates by design and is super smooth to integrate into your project.

Here’s everything you need to do:

  • Create a free Mailtrap account.
  • Verify your domain and update DNS records.
  • Navigate to the Sending Domains Integration and choose your preferred stream.
  • Click on API and select Go underneath Code Samples.

Then, simply copy the code snippet below into your main.go file to start sending basic HTML messages:

package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"net/http"
	"time"
)

func main() {
	// Mailtrap account config
	token := "<secret_token>"
	httpHost := "https://send.api.mailtrap.io/api/send"

	// Message body with HTML content
	message := []byte(`{
		"from":{"email":"john.doe@your.domain"},
		"to":[{"email":"kate.doe@example.com"}],
		"subject":"Why aren't you using Mailtrap yet?",
		"text":"Here's the space for your great sales pitch",
		"html":"<strong>Here's the space for your great sales pitch</strong>"
	}`)

	// Set up request
	request, err := http.NewRequest(http.MethodPost, httpHost, bytes.NewBuffer(message))
	if err != nil {
		log.Fatal(err)
	}

	// Set required headers
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Authorization", "Bearer "+token)

	// Send request
	client := http.Client{Timeout: 5 * time.Second}
	res, err := client.Do(request)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()

	body, err := io.ReadAll(res.Body)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(body))
}

Last but not least, replace the placeholders such as token and from:email with your actual Mailtrap credentials, then start the server and make requests by running main.go.

Also, as you can notice, I’ve kept the "text" field as an industry-standard practice, acting as a fallback in case some of your recipients’ email clients do not support HTML.

Send HTML email with attachment

For sending HTML emails with attachments, I’ve added the attachments field under html

Check out the code snippet below:

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"time"
)

// within main function
	// Open the file
	file, err := os.Open("attachment.txt")
	if err != nil {
		log.Fatalf("Failed to open file: %v", err)
	}
	defer file.Close()

	// Read all contents of the file
	fileData, err := io.ReadAll(file)
	if err != nil {
		log.Fatalf("Failed to read file: %v", err)
	}

	// Encode the file data to base64
	encodedFileData := base64.StdEncoding.EncodeToString(fileData)

message := []byte(`{
    "from": { "email": "john.doe@your.domain" },
    "to": [
        { "email": "kate.doe@example.com" }
    ],
    "subject": "Here's your attached file!",
    "text": "Check out the attached file.",
    "html": "<p>Check out the attached <strong>file</strong>.</p>",
    "attachments": [
        {
          "filename": "example.pdf",
          "content": "` + encodedFileData + `",
          "type": "application/pdf",
          "disposition": "attachment"
        }
    ]
}`)

Send HTML email with embedded image

To embed an image to our HTML email and display it inline, we’ll specify the desired image’s file path, set disposition to “inline”, include an <img> tag, and use the select file type.

Sounds complicated? Don’t worry—here’s a code snippet you can use:

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
)

// within main function
	// Open the file (image instead of a generic attachment)
	file, err := os.Open("image.jpg") // Replace with your image file path
	if err != nil {
		log.Fatalf("Failed to open file: %v", err)
	}
	defer file.Close()

	// Read all contents of the file
	fileData, err := io.ReadAll(file)
	if err != nil {
		log.Fatalf("Failed to read file: %v", err)
	}

	// Encode the file data to base64
	encodedFileData := base64.StdEncoding.EncodeToString(fileData)

message := []byte(`{
    "from": { "email": "john.doe@your.domain" },
    "to": [
        { "email": "kate.doe@example.com" }
    ],
    "subject": "Here's an embedded image!",
    "text": "Check out the embedded image.",
    "html": "<p>Here's an embedded image:</p><img src='cid:embedded-image' alt='Embedded Image' />",
    "attachments": [
        {
          "filename": "image.jpg", // Use the actual image filename
          "content": "` + encodedFileData + `",
          "type": "image/jpeg", // Adjust type if using a different image format
          "disposition": "inline",
          "content_id": "embedded-image"
        }
    ]
}`)

Code breakdown:

  • File path – Added path to the image file (image.jpg).
  • Inline display – Set "disposition": "inline" to display the image inline.
  • Content ID – Added "content_id": "embedded-image" as a unique identifier for our image in the email body.
  • HTML body – Included an <img> tag referencing content_id with src='cid:embedded-image'
  • Attachment type – Set the "type" to "image/jpeg" for the image MIME type.
    • You can adjust this accordingly if you’re using another image format (e.g., PNG).

For more sending cases like asynchronous or bulk email, be sure to check out our dedicated article. 👀

Sending HTML email with dynamic content

To send HTML email with dynamic content, I’ve added variables such as recipientEmail and recipientName and fmt.Sprintf, which will substitute these variables into the JSON payload.

Check it out in action:

package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Mailtrap account config
	token := "<secret_token>"
	httpHost := "https://send.api.mailtrap.io/api/send"

	// Dynamic variables
	recipientEmail := "kate.doe@example.com"
	recipientName := "Kate"

	// Message body with dynamic content
	message := []byte(fmt.Sprintf(`{
		"from":{"email":"john.doe@your.domain"},
		"to":[{"email":"%s"}],
		"subject":"Why aren't you using Mailtrap yet?",
		"text":"Hi %s, here's the space for your great sales pitch.",
		"html":"<p>Hi <strong>%s</strong>,</p><p>Here's the space for your great sales pitch.</p>"
	}`, recipientEmail, recipientName, recipientName))

	// Set up request
	request, err := http.NewRequest(http.MethodPost, httpHost, bytes.NewBuffer(message))
	if err != nil {
		log.Fatal(err)
	}

	// Set required headers
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Authorization", "Bearer "+token)

	// Send request
	client := http.Client{}
	res, err := client.Do(request)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()

	body, err := io.ReadAll(res.Body)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(body))
}

Test emails before sending

Now, if you think this is a lot of email-sending code, you’re not wrong. And where there’s a lot of code, there are many potentials for errors. 🏮

That’s why I always recommend testing your emails before sending them out. This ensures your HTML/CSS designs are flawless when they reach your recipients’ inboxes, that your emails pass spam filters, and that your domain isn’t getting blacklisted.

For this, I personally use Mailtrap Email Testing, another inseparable part of the Mailtrap Email Delivery Platform.

With Mailtrap Email Testing, you can preview your emails in source HTML, text, raw, and more — all before sending them out to your recipients.

The HTML Check feature is also super useful for fixing/removing any faulty lines of code, which ensures your emails are pitch-perfect, no matter what browser or device your recipients are using.

Mailtrap Email Testing also provides you with a spam score. If you keep it below 5, you significantly reduce the potential number of email deliverability issues once your app moves to production.

There’s also the Email Templates feature, which lets you test your emails with our API and then easily switch from staging to production when you think you’re ready, saving time on crafting your campaigns.

Now, let me show you how to set up Mailtrap Email Testing!

SMTP

First, create a free Mailtrap account and navigate to Email Testing Inboxes Integration.

Then, simply copy the provided fake SMTP credentials into your Gomail configuration, that is, in your dialer method. Just like this:

dialer := gomail.NewDialer("sandbox.smtp.mailtrap.io", 587, "ae54a7713017172", "1a2b3c4d5e6f7g")

API

To integrate Mailtrap API for testing, automation, and testing automated sequences, simply navigate to Email Testing InboxesIntegration, select API, then Go underneath the Code Samples.

Then, simply copy the snippet below into your main.go file:

package main

import (
	"fmt"
	"strings"
	"net/http"
	"io/ioutil"
)

func main() {

	url := "https://sandbox.api.mailtrap.io/api/send/2804194"
	method := "POST"

	payload := strings.NewReader(`{\"from\":{\"email\":\"hello@example.com\",\"name\":\"Mailtrap Test\"},\"to\":[{\"email\":\"demo@mailtrap.io\"}],\"subject\":\"You are awesome!\",\"text\":\"Congrats for sending test email with Mailtrap!\",\"category\":\"Integration Test\"}`)

	client := &http.Client {
	}
	req, err := http.NewRequest(method, url, payload)

	if err != nil {
		fmt.Println(err)
		return
	}
	req.Header.Add("Authorization", "Bearer 0e0****************************as9")
	req.Header.Add("Content-Type", "application/json")

	res, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(string(body))
}

For more sending examples, use cases, and information, visit the official API documentation.

Wrapping up

And with that, we’ve covered everything you need to know to send HTML emails in Go!

Now, you can add sending functionality both via SMTP and API, and you know how to use dynamic content, email templates, and more! Just be sure to test everything out once you have your logic in place, as you don’t want your recipients to receive HTML emails that are all over the place.

Also, be sure to visit our blog, where you can find other HTML-related articles such as:

And if you’re looking for some eye bleach, our YouTube channel might be just the thing you need! 👀

Ivan Djuric, an author at Mailtrap
Article by Ivan Djuric Technical Content Writer @Mailtrap

I’m a Technical Content Writer with 5 years of background covering email-related topics in tight collaboration with software engineers and email marketers. I just love to research and share actionable insights with you about email sending, testing, deliverability improvements, and more. Happy to be your guide in the world of emails!