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