All Products
Search
Document Center

Direct Mail:Call SMTP using Go: Sample code

Last Updated:Jun 09, 2026

Send email from a Go application using the Direct Mail SMTP service. Two complete samples cover port 80 (plain connection with PLAIN or LOGIN authentication) and port 465 (implicit TLS).

Direct Mail exposes two SMTP endpoints. Use port 80 for a plain connection with PLAIN or LOGIN authentication. Use port 465 for implicit TLS, where the TLS handshake occurs before any SMTP commands. Both samples are verified on Go 1.16.5 and use smtpdm.aliyun.com as the SMTP host.

Prerequisites

Before you begin, make sure you have:

  • A verified sender address configured in the Direct Mail console

  • The SMTP password for that sender address

Use port 80

The sample below sends an HTML email with To, CC, and BCC recipients. MergeSlice combines all three recipient lists into a single SMTP envelope recipient list. This is required because the SMTP RCPT TO command determines which mailboxes receive the message, regardless of what the email headers show.

Replace the placeholder strings in main() with your sender address and SMTP password from the Direct Mail console.

Note

To send over an encrypted connection, use the port 465 sample below. Do not use the port 80 code for TLS connections.

package main

import (
    "fmt"
    "net/smtp"
    "strings"
    "time"
)

func SendToMail(user, password, host, subject, date, body, mailtype, replyToAddress string, to, cc, bcc []string) error {
    hp := strings.Split(host, ":")
    auth := smtp.PlainAuth("", user, password, hp[0])
    var content_type string
    if mailtype == "html" {
        content_type = "Content-Type: text/" + mailtype + "; charset=UTF-8"
    } else {
        content_type = "Content-Type: text/plain" + "; charset=UTF-8"
    }

    cc_address := strings.Join(cc, ";")
    bcc_address := strings.Join(bcc, ";")
    to_address := strings.Join(to, ";")
    msg := []byte("To: " + to_address + "\r\nFrom: " + user + "\r\nSubject: " + subject+ "\r\nDate: " + date + "\r\nReply-To: " + replyToAddress + "\r\nCc: " + cc_address + "\r\nBcc: " + bcc_address + "\r\n" + content_type + "\r\n\r\n" + body)
    send_to := MergeSlice(to, cc)
    send_to = MergeSlice(send_to, bcc)
    err := smtp.SendMail(host, auth, user, send_to, msg)
    return err
}

func main() {
    user := "The sender address created in the console"
    password := "The SMTP password set in the console"
    host := "smtpdm.aliyun.com:80"
    to := []string{"recipient_address","recipient_address_1"}
    cc := []string{"cc_address","cc_address_1"}
    bcc := []string{"bcc_address","bcc_address_1"}
    subject := "test Golang to sendmail"
    date := fmt.Sprintf("%s", time.Now().Format(time.RFC1123Z))
    mailtype :="html"
    replyToAddress:="a***@example.net"
    body := "<html><body><h3>Test send to email</h3></body></html>"
    fmt.Println("send email")
    err := SendToMail(user, password, host, subject,date, body, mailtype, replyToAddress, to, cc, bcc)
    if err != nil {
        fmt.Println("Send mail error!")
        fmt.Println(err)
    } else {
        fmt.Println("Send mail success!")
    }
}

func MergeSlice(s1 []string, s2 []string) []string {
    slice := make([]string, len(s1)+len(s2))
    copy(slice, s1)
    copy(slice[len(s1):], s2)
    return slice
}

Go 1.9.2 compatibility note

Note

Go 1.9.2 requires encrypted authentication and rejects a plain PlainAuth call with an "unencrypted connection" error. To resolve this, replace the smtp.PlainAuth call with the custom LoginAuth implementation below.

Add the following LoginAuth implementation to your file:

type loginAuth struct {
    username, password string
}

func LoginAuth(username, password string) smtp.Auth {
    return &loginAuth{username, password}
}

func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
    // return "LOGIN", []byte{}, nil
    return "LOGIN", []byte(a.username), nil
}

func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
    if more {
        switch string(fromServer) {
        case "Username:":
            return []byte(a.username), nil
        case "Password:":
            return []byte(a.password), nil
        }
    }
    return nil, nil
}

Then replace the auth line in main() with:

auth := LoginAuth(user, password)

Use port 465

Port 465 uses implicit TLS: the TLS handshake happens before any SMTP commands, so net/smtp's built-in SendMail cannot be used directly. The sample defines a custom Dial function that opens a TLS connection using tls.Dial, and a SendMailUsingTLS function that drives the SMTP session manually.

package main

import (
    "crypto/tls"
    "fmt"
    "log"
    "net"
    "net/smtp"
)

func main() {
    host := "smtpdm.aliyun.com"
    port := 465
    email := "test***@example.net"
    password := "TExxxxxst"
    toEmail := "test1***@example.net"

    header := make(map[string]string)
    header["From"] = "test" + "<" + email + ">"
    header["To"] = toEmail
    header["Subject"] = "Email Title"
    header["Content-Type"] = "text/html; charset=UTF-8"

    body := "This is a test email!"
    message := ""
    for k, v := range header {
        message += fmt.Sprintf("%s: %s\r\n", k, v)
    }
    message += "\r\n" + body

    auth := smtp.PlainAuth(
        "",
        email,
        password,
        host,
    )

    err := SendMailUsingTLS(
        fmt.Sprintf("%s:%d", host, port),
        auth,
        email,
        []string{toEmail},
        []byte(message),
    )

    if err != nil {
        panic(err)
    } else {
        fmt.Println("Send mail success!")
    }
}

// Dial opens an implicit TLS connection and returns an SMTP client.
func Dial(addr string) (*smtp.Client, error) {
    conn, err := tls.Dial("tcp", addr, nil)
    if err != nil {
        log.Println("Dialing Error:", err)
        return nil, err
    }
    // Split the host and port from the address string.
    host, _, _ := net.SplitHostPort(addr)
    return smtp.NewClient(conn, host)
}

// SendMailUsingTLS sends an email over an implicit TLS connection.
// When you use net. Dial to connect to a TLS (SSL) port, smtp.NewClient() may hang without returning an error.
// If len(to) is greater than 1, recipients from to[1] onwards are treated as BCC recipients.
func SendMailUsingTLS(addr string, auth smtp.Auth, from string,
    to []string, msg []byte) (err error) {

    // Create the SMTP client.
    c, err := Dial(addr)
    if err != nil {
        log.Println("Create smpt client error:", err)
        return err
    }
    defer c.Close()

    if auth != nil {
        if ok, _ := c.Extension("AUTH"); ok {
            if err = c.Auth(auth); err != nil {
                log.Println("Error during AUTH", err)
                return err
            }
        }
    }

    if err = c.Mail(from); err != nil {
        return err
    }

    for _, addr := range to {
        if err = c.Rcpt(addr); err != nil {
            return err
        }
    }

    w, err := c.Data()
    if err != nil {
        return err
    }

    _, err = w.Write(msg)
    if err != nil {
        return err
    }

    err = w.Close()
    if err != nil {
        return err
    }

    return c.Quit()
}