email

package module
v0.0.0-...-f92ec04 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 5, 2026 License: MIT Imports: 7 Imported by: 0

README

go-simple-mail

A simple email client package written in Go

There are lots of email client packages available for Go but many of them are no longer supported (with issues open for a long time) and/or badly tested. Some don't support attachments.

The package github.com/wneessen/go-mail is well supported, well tested and supports attachments. However, it's not very well documented. I had to trawl through the unit tests to figure out how to use it.

This package is a wrapper around the wneesson package which I claim is easier to understand and use.

The wneesson package has very few dependencies and I've avoided adding unnecessary ones in this package. It needs to figure out the content type of the body text and the attachment files - plain text, HTML or whatever. To do that use github.com/gabriel-vasile/mimetype.

Email servers offer various styles of connection and if you are using a commercial service you may not have any choice which one is used. This package supports a plain text connection, a TLS connection or an SSL connection, all secured by user name and password. I only have mail servers available that offer TLS connections, which is the standard these days, so that's the only one that I can test.

Other styles of connection are not supported here. If you need a different style you need to call wneesson's go-mail directly. This package may be a useful guide as to how to do that.

Installation

    git clone https://github.com/goblimey/go-simple-mail.git
    cd go-simple-mail
    go get github.com/wneessen/go-mail
    go get github.com/gabriel-vasile/mimetype

Handling Secrets

The example below is nice and simple but it doesn't handle its secrets well.

Secrets are values that need to be kept secret - for example, user names and passwords. There is lots of advice out there about handling them securely

It's worth pointing out that if the source code of a program contains bits of text that contain secrets (as in the example) it's possible to find them in the compiled binary and extract them. If somebody breaks into your system and they can read the binary file, they can extract any secrets embedded in it.

The general advice is that the secrets should be fed into the program at run time via environment variables. For extra security, these can be set from an external secrets store.

For more detailed advice see this Reddit thread

You don't want to leave secret text lying around in the program's memory when it's no longer needed. For example, if your program opens a connection to the mail server just once at the start and uses it for the rest of the run, you don't need the password once the connection is open. Clearing it out is not as easy as it looks in Go. See this package for how to do it properly.

Sending an email with an attachment

    import (
        "os"
        "log/slog"
        "time"
        "github.com/goblimey/go-simple-mail" // Package email
    )
    const fromAddr = "johnsmith@gmail.com"
    const toAddr = "janesmith@gmail.com"
    const subject = "the email subject"

    // Create an optional slog logger.  The Config type supplies methods such as LogError 
    // which check for a nil logger before trying to use it.
    logger := slog.New(slog.NewTextHandler(os.Stdout, nil))

    // Configure the email connection.  This one connects to John Smith's
    // gmail account via a TLS connection, authenticating with his Google password.
    cfg := email.NewConfigWithLogger(
        "smtp.gmail.com",   // mail server.
        465,                // Port.
        fromAddr, 
        "googlepassword",   // This should be supplied as a secret.
        true,               // Secure connection.
        true,               // Secured via TLS.
        5 * time.Second,    // Connection timeout.
        logger,
    )

    connection, connError := email.Connect(cfg)
    if connError != nil {
        cfg.LogError(connError.Error())
        return
    }
    defer connection.Close()

    attachment, attError := email.NewAttachment("./attachment.txt", "attachment.txt")
    if attError != nil {
        cfg.LogError(attError.Error())
        return
    }

    attachments := []email.Attachment{*attachment}

    const body = "<html><h1>this is a heading</h1>this is the running text</html>"

    em := email.NewEmail(cfg, fromAddr, toAddr, "the subject", body, attachments)

    if err := em.Send(connection); err != nil {
        cfg.LogError(err.Error())
        return
    }

    connection.Close()

Content Type

The underlying mailer github.com/wneessen/go-mail needs to be given the content type of the body of the email and any attached files.

This package uses github.com/gabriel-vasile/mimetype to figure out content types, but if the content is supplied as a file, that involves opening the file and reading it.

The calling program can create an email object and use it over and over, for example to send the same email to more than one address. The first call of Send figures out the content type and stores it in the email object. Subsequent calls pick up the content type and avoid the overhead of finding it again. Similarly, NewAttachment figures out the content type of the attached file and stores it in the Attachment object for reuse.

If you know the content type you can supply it. Create the email object with NewEmailWithBodyContentType instead of NewEmail.

The NewAttachmentWithContentType creates an Attachment object and allows you to set the content type to your chosen value.

To figure out the content type of a string, you need some extra imports:

    import (
		"os"
        "log/slog"
        "time"
        "github.com/goblimey/go-simple-mail" // Package email
        "github.com/wneessen/go-mail"        // Package mail.
        "github.com/gabriel-vasile/mimetype" // Package mimetype.
    )

Then get the content type like so:

    const body = "<html><h1>this is a heading</h1>this is the running text</html>"
    mimeTypeOfBody := mimetype.Detect([]byte(body))
    contentTypeOfBody := mail.ContentType(mimeTypeOfBody.String())

To figure out the content type of a file, given the name in filename, open the file and use DetectReader:

    var openError error
    var f *os.File
    f, openError = os.Open(filename)
    if openError != nil {
        return "", openError
    }
    defer f.Close()
    var detectError error
    var mimeTypeOfAttachment *mimetype.MIME
    mimeTypeOfAttachment, detectError = mimetype.DetectReader(f)
    if detectError != nil {
        return "", detectError
    }

Whichever account you connect with, many email providers restrict the sending addresses you can use. Maybe you have to use the same address or maybe you have to use an address in the same domain. If you want replies to go to some other email address, use ReplyTo.

When you send an email with an attachment, you specify the pathname of the file on your machine and the name that you suggest that the receiving email client might use when it downloads the file. In this example, the local file is "./atch.txt" and the suggested name is "atch.txt".

Documentation

Overview

email provides a simple interface to W Neeson's go-mail package. Unlike many Go email packages, go-mail is well tested, well maintained and supports the use of attachments. However, it offers little documentation other than the unit tests and I had tomspen quite a lot of time figuring out how to use it.

Email servers offer various styles of connection and if you are using a commercial service you may not have any choice which one is used. This package supports a plain text connection, a TLS connection or an SSL connection, all secured by user name and password. Other styles of connection are not supported. If you need a different style, you need to call go-mail directly, but this package may be a useful guide as to how to do that.

Index

Constants

View Source
const SendErrorLeader = "email.Send: failed to send mail to "

SendErrorLeader is the start of a error message which can be watched for by the calling package. It indicates that the mail failed at the sending stage, which would not necessarily stop other emails being sent, rather than an earlier when the connection to the server was set up.

Variables

This section is empty.

Functions

func ContentTypeOfFile

func ContentTypeOfFile(filename string) (mail.ContentType, error)

ContentTypeOfFile returns the content type of the file with the given name.

Types

type Attachment

type Attachment struct {
	Filename         string           // The path name of the stored file on this server.
	OriginalFilename string           // The suggested file name to be used when downloaded by the receiving mail client.
	ContentType      mail.ContentType // The content type of the file.
}

Attachment contains an attachment and its content type. Files to be attached are stored in the directory given by the config. The name here is relative to that directory.

func NewAttachment

func NewAttachment(filename, originalFilename string) (*Attachment, error)

NewAttachment creates and returns a new Attachment. The filename is the pathname of the attached file on the local machine. The original filename is the suggested name that the receiving email client might use to store the file when downloaded. The function opens and reads the file to figure out the content type. That overhead is avoided if you use NewAttachmentWithContentType instead. Finding the content type can produce an error, for example if the file doesn't exist. Any such error is returned.

func NewAttachmentWithContentType

func NewAttachmentWithContentType(filename, originalFilename string, contentType mail.ContentType) *Attachment

NewAttachmentWithContentType creates and returns a new Attachment. This is an alternative to NewAttachment, useful if the content type of the file is known, as it avoid the overhead of figuring it out. The given filename is the pathname of the file to be attached on the local machine. The given originalFilename is the suggested name that the receiving email client might use to store the file when downloaded. The given contentType is the content type of the file (plain text, HTML or whatever) as defined by the package github.com/gabriel-vasile/mimetype. It's the responsibility of the caller to get the content type right.

type Config

type Config struct {
	Host              string        // The email server.
	Port              int           // The email port on the email server.
	User              string        // The account used on the email server.
	Password          string        // The password of the sending account.
	SecureConnection  bool          // The connection is secure (TLS or SSL).
	TLS               bool          //The connection is secured using TLS - if false, then by SSL.
	ConnectionTimeout time.Duration // The email timeout as a duration.
	Logger            *slog.Logger  // The optional logger.
}

func NewConfig

func NewConfig(host string, port int, user, password string, secure, tls bool, timeout time.Duration) *Config

func NewConfigWithLogger

func NewConfigWithLogger(host string, port int, user, password string, secure, tls bool, timeout time.Duration, logger *slog.Logger) *Config

func (*Config) LogDebug

func (cfg *Config) LogDebug(s string)

LogDebug logs the string at Debug level.

func (*Config) LogError

func (cfg *Config) LogError(s string)

LogError logs the string at Error level.

func (*Config) LogInfo

func (cfg *Config) LogInfo(s string)

LogInfo logs the string at Info level.

type Connection

type Connection struct {
	Cfg    *Config      // The configuration.
	Client *mail.Client // The running SMTP client.
}

func Connect

func Connect(cfg *Config) (*Connection, error)

func (*Connection) Close

func (c *Connection) Close() error

Close the client.

type Email

type Email struct {
	Cfg             *Config
	Subject         string
	From            string
	To              string
	ReplyTo         string
	Body            string
	BodyContentType mail.ContentType
	Attachments     []Attachment
}

Email represents a single email - From address, To address, subject etc.

func NewEmail

func NewEmail(cfg *Config, from, replyTo, to, subject, body string, attachments []Attachment) *Email

// NewMail creates an Email and sets the content type of the body.

func NewEmailWithBodyContentType

func NewEmailWithBodyContentType(cfg *Config, from, replyTo, to, subject, body string,
	attachments []Attachment, bodyContentType mail.ContentType) *Email

NewMailWithBodyContentType creates an Email and sets the content type of the body.

func (*Email) Send

func (em *Email) Send(connection *Connection) error

Send sends the email.

Directories

Path Synopsis
code
apps/mailer command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL