Net/SMTP packages

Introduction to the

Introduction of agreement

Schematic diagram of SMTP sending mail flow instruction:

Introduction of package

SMTP is a Simple Mail Transfer Protocol (SMTP) package that complies with RFC5321 and related extensions comply with related RFC documents

8BITMIME RFC 1652

AUTH RFC 2554

STARTTLS RFC 3207

function

SendMail

SMTP package encapsulated a mail sending method SendMail, call this method can directly SendMail, this method in accordance with the SMTP protocol request steps were encapsulated, so the caller does not have to understand the specific SMTP protocol send steps can directly SendMail. The SendMail function and NET/SMTP packages do not support DKIM signatures, MIME attachments (see MIME/Multipart packages), or other mail features. Higher-level packages exist outside the standard library

func SendMail(addr string, a Auth, from string, to []string, msg []byte) error
Copy the code
See the sample
package main

import (
	"log"
	"net/smtp"
)

func main(a) {
	// Set PlainAuth authentication account and SMTP server information
	auth := smtp.PlainAuth(""."[email protected]"."password"."mail.example.com")

	// Set the sender. Each email address in the array will be RCPT called
	to := []string{"[email protected]"}

    // Write the sent message
	msg := []byte("To: [email protected]\r\n" +
		"Subject: discount Gophers! \r\n" +
		"\r\n" +
		"This is the email body.\r\n")

    // Call the function to send mail
	err := smtp.SendMail("mail.example.com:25", auth, "[email protected]", to, msg)
	iferr ! =nil {
		log.Fatal(err)
	}
}
Copy the code

The content in the MSG above must conform To the content specification of SMTP protocol, To code the person who receives the mail, and Subject represents the Subject of the mail. The content section is symbolized. At the end. Refer to RFC 822 Tips: The way to send a “BCC” message is to include an E-mail address in the to argument, but not in the MSG header. That is, only the RCPT call is made and the address is not noted in the message.

Auth interface

The Auth interface has two methods, Start and Next.

  • The Start method indicates that the server is being authenticated. It returns the name of the authentication protocol, as well as the data optionally included in the initial AUTH message sent to the server. It can return proto == “” to indicate that authentication has been skipped. If the error returned is not nil, the SMTP client terminates the authentication attempt and closes the connection.

  • The Next method indicates that authentication continues, and the server sends formServer data. When the MORE field is true, it indicates that it wants to receive response data, which is returned in []byte data format. Returns nil when the MORE field is false. If the error returned is not nil, the SMTP client terminates the authentication attempt and closes the connection.

View Auth interface source code
type Auth interface {

    Start(server *ServerInfo) (proto string, toServer []byte, err error)

    Next(fromServer []byte, more bool) (toServer []byte, err error)
}
Copy the code

CRAMMD5Auth

CRAMMD5Auth returns the Auth that implements the CRAM-MD5 authentication mechanism defined in RFC 2195.

CRAMMD5Auth implements two methods of the Auth interface

Click on CRAMMD5Auth source code
type cramMD5Auth struct {
	username, secret string
}
// Provide externally called methods, passing in username and secret, and the returned Auth authenticates the server using challenge-response mechanisms with the given username and password.
func CRAMMD5Auth(username, secret string) Auth {
	return &cramMD5Auth{username, secret}
}
// Implement Auth's Start method, return the protocol name 'CRAM-MD5'
func (a *cramMD5Auth) Start(server *ServerInfo) (stringAnd []byte, error) {
	return "CRAM-MD5".nil.nil
}
// Implement Auth's Next method to encrypt
func (a *cramMD5Auth) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		d := hmac.New(md5.New, []byte(a.secret))
		d.Write(fromServer)
		s := make([]byte.0, d.Size())
		return []byte(fmt.Sprintf("%s %x", a.username, d.Sum(s))), nil
	}
	return nil.nil
}
Copy the code

PlainAuth

PlainAuth returns an Auth that implements the definition of RFC 4616.

PlainAuth sends credentials only if the connection uses TLS or connects to a localhost. Otherwise, authentication will fail with an error and credentials will not be sent.

PlainAuth implements two methods of the Auth interface

View the PlainAuth source code
type plainAuth struct {
	identity, username, password string
	host                         string
}
// Exposes methods to external calls, generally identity is an empty string. Pass in the account name, password, and SMTP server address
func PlainAuth(identity, username, password, host string) Auth {
	return &plainAuth{identity, username, password, host}
}
// Check whether it is a local address
func isLocalhost(name string) bool {
	return name == "localhost" || name == "127.0.0.1" || name == : : "1"
}
// Implement the Start method of the Auth interface to return the PLAIN protocol and the data in the initial Auth message sent to the server
func (a *plainAuth) Start(server *ServerInfo) (stringAnd []byte, error) {
	if! server.TLS && ! isLocalhost(server.Name) {return "".nil, errors.New("unencrypted connection")}ifserver.Name ! = a.host {return "".nil, errors.New("wrong host name")
	}
	resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password)
	return "PLAIN", resp, nil
}

func (a *plainAuth) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		return nil, errors.New("unexpected server challenge")}return nil.nil
}
Copy the code

The Client structure

View the Client structure
type Client struct {
	// The textProto.conn used by the client. It is exported to allow clients to add extensions.
	Text *textproto.Conn
	// Keep a reference to the connection for later creation of TLS links
	conn net.Conn
	// Whether the client is using TLS
	tls        bool
	serverName string
	// Support extended Map
	ext map[string]string
	// Support auth mechanism
	auth       []string
	localName  string
    // Whether HELLO/EHLO has been called
	didHello   bool   
    // HELO response error
	helloError error
}
Copy the code

Dial

This function will call the [net.Dial function, establish a TCP connection with the incoming SMTP address (the address must contain the port, for example: smtp.qq.com:25), and return an SMTP client by calling NewClient

View the source code of the Dial function
func Dial(addr string) (*Client, error) {
	conn, err := net.Dial("tcp", addr)
	iferr ! =nil {
		return nil, err
	}
	host, _, _ := net.SplitHostPort(addr)
	return NewClient(conn, host)
}
Copy the code

NewClient

This function returns a new SMTP client using an existing TCP connection to the SMTP server

View the NewClient function source code
func NewClient(conn net.Conn, host string) (*Client, error) {
	text := textproto.NewConn(conn)
	_, _, err := text.ReadResponse(220)
	iferr ! =nil {
		text.Close()
		return nil, err
	}
	c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
	_, c.tls = conn.(*tls.Conn)
	return c, nil
}
Copy the code

(*Client) Auth

Auth uses the provided authentication mechanism to authenticate the client. If the authentication fails, the client connection is closed. Only SMTP servers that support Auth extension can use this function.

View (*Client) Auth source code
func (c *Client) Auth(a Auth) error {
	iferr := c.hello(); err ! =nil {
		return err
	}
	encoding := base64.StdEncoding
	mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth})
	iferr ! =nil {
		c.Quit()
		return err
	}
	resp64 := make([]byte, encoding.EncodedLen(len(resp)))
	encoding.Encode(resp64, resp)
	code, msg64, err := c.cmd(0, strings.TrimSpace(fmt.Sprintf("AUTH %s %s", mech, resp64)))
	for err == nil {
		var msg []byte
		switch code {
		case 334:
			msg, err = encoding.DecodeString(msg64)
		case 235:
			// the last message isn't base64 because it isn't a challenge
			msg = []byte(msg64)
		default:
			err = &textproto.Error{Code: code, Msg: msg64}
		}
		if err == nil {
			resp, err = a.Next(msg, code == 334)}iferr ! =nil {
			// abort the AUTH
			c.cmd(501."*")
			c.Quit()
			break
		}
		if resp == nil {
			break
		}
		resp64 = make([]byte, encoding.EncodedLen(len(resp)))
		encoding.Encode(resp64, resp)
		code, msg64, err = c.cmd(0.string(resp64))
	}
	return err
}
Copy the code

(*Client) Close

This method is used to close the connection between the client and the SMTP server

View (*Client) Close source code
func (d *dataCloser) Close(a) error {
	d.WriteCloser.Close()
	_, _, err := d.c.Text.ReadResponse(250)
	return err
}
Copy the code

(*Client) Data

This method issues THE SMTP DATA command to the SMTP server and returns a writer that can be used to write the header and body of the message. The caller should close the writer before calling any other methods. One or more calls to Rcpt must be made before Data can be called. The returned IO.WriteCloser shall comply with RFC 822 specification when writing data

View (*Client) Data source code
func (c *Client) Data(a) (io.WriteCloser, error) {
	_, _, err := c.cmd(354."DATA")
	iferr ! =nil {
		return nil, err
	}
	return &dataCloser{c, c.Text.DotWriter()}, nil
}
Copy the code

(*Client) Extension

This method is used to check whether the SMTP server supports the extension (the passed extension name is case insensitive). If the extension is supported, it returns true and the value string under the key of the corresponding SMTP server; if not, it returns false and an empty string. Such as:

// Check whether the 'SMTP' server of QQ mailbox supports' AUTH 'extension
client,_:=smtp.Dial("smtp.qq.com:25")
b,p:=client.Extension("auth")
log.Println(b)
log.Println(p)

/ / output
true
LOGIN PLAIN
Copy the code
Check the (*Client) Extension source code
func (c *Client) Extension(ext string) (bool.string) {
	iferr := c.hello(); err ! =nil {
		return false.""
	}
	if c.ext == nil {
		return false.""
	}
	ext = strings.ToUpper(ext)
	param, ok := c.ext[ext]
	return ok, param
}
Copy the code

(*Client) Hello

This method sends HELO/EHLO instructions to the SMTP server and needs to be called before any methods can be called

View (*Client) Hello source code
func (c *Client) hello(a) error {
	if! c.didHello { c.didHello =true
		err := c.ehlo()
		iferr ! =nil {
			c.helloError = c.helo()
		}
	}
	return c.helloError
}
Copy the code

(*Client) Mail

Mail issues the Mail command to the server using the E-mail address provided. If the server supports the 8BITMIME extension, Mail will add the BODY = 8BITMIME parameter. If the server supports the SMTPUTF8 extension, Mail will add the SMTPUTF8 parameter. This will start the mail transaction and then make one or more Rcpt calls.

View (*Client) Mail source code
func (c *Client) Mail(from string) error {
	iferr := validateLine(from); err ! =nil {
		return err
	}
	iferr := c.hello(); err ! =nil {
		return err
	}
	cmdStr := "MAIL FROM:<%s>"
	ifc.ext ! =nil {
		if _, ok := c.ext["8BITMIME"]; ok {
			cmdStr += " BODY=8BITMIME"
		}
		if _, ok := c.ext["SMTPUTF8"]; ok {
			cmdStr += " SMTPUTF8"
		}
	}
	_, _, err := c.cmd(250, cmdStr, from)
	return err
}
Copy the code

(*Client) Noop

This method sends the NOOP command to the SMTP server and does nothing but check whether the connection to the SMTP server is normal

View (*Client) Noop source code
func (c *Client) Noop(a) error {
	iferr := c.hello(); err ! =nil {
		return err
	}
	_, _, err := c.cmd(250."NOOP")
	return err
}
Copy the code

(*Client) Quit

Send the QUIT command to the SMTP server to close the connection between the client and the SMTP server

View (*Client) Quit source code
func (c *Client) Quit(a) error {
	iferr := c.hello(); err ! =nil {
		return err
	}
	_, _, err := c.cmd(221."QUIT")
	iferr ! =nil {
		return err
	}
	return c.Text.Close()
}
Copy the code

(*Client) Rcpt

Sends RCPT commands to the SMTP server for the email addresses to be received

You need to call the Mail method first and then this method

View (*Client) Rcpt source code
func (c *Client) Rcpt(to string) error {
	iferr := validateLine(to); err ! =nil {
		return err
	}
	_, _, err := c.cmd(25."RCPT TO:<%s>", to)
	return err
}
Copy the code

(*Client) Reset

The RSET command is sent to the SMTP server to abort the current mail transaction.

View (*Client) Reset source
func (c *Client) Reset(a) error {
	iferr := c.hello(); err ! =nil {
		return err
	}
	_, _, err := c.cmd(250."RSET")
	return err
}
Copy the code

(*Client) StartTLS

Send the STARTTLS command and encrypt all subsequent communications. Only SMTP servers that publish STARTTLS extensions support this feature.

Example:

// Enable 'TLS' for QQ mailbox
client,err:=smtp.Dial("smtp.qq.com:25")
config := &tls.Config{ServerName: "smtp.qq.com"}
client.StartTLS(config)
Copy the code
View (*Client) Reset source
func (c *Client) StartTLS(config *tls.Config) error {
	iferr := c.hello(); err ! =nil {
		return err
	}
	_, _, err := c.cmd(220."STARTTLS")
	iferr ! =nil {
		return err
	}
	c.conn = tls.Client(c.conn, config)
	c.Text = textproto.NewConn(c.conn)
	c.tls = true
	return c.ehlo()
}
Copy the code

(*Client) TLSConnectionState

Check the TLS connection status of the client. If there is no connection to TLS, the second parameter returns false.

View (*Client) Reset source
func (c *Client) TLSConnectionState(a) (state tls.ConnectionState, ok bool) {
	tc, ok := c.conn.(*tls.Conn)
	if! ok {return
	}
	return tc.ConnectionState(), true
}
Copy the code

(*Client) Verify

Check if the mail address is valid, and if error is returned nil, the address is valid.

This method is generally not used

View (*Client) Reset source
func (c *Client) Verify(addr string) error {
	iferr := validateLine(addr); err ! =nil {
		return err
	}
	iferr := c.hello(); err ! =nil {
		return err
	}
	_, _, err := c.cmd(250."VRFY %s", addr)
	return err
}
Copy the code