// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package smtp implements the Simple Mail Transfer Protocol as defined in RFC 5321. // It also implements the following extensions: // // 8BITMIME RFC 1652 // AUTH RFC 2554 // STARTTLS RFC 3207 // // Additional extensions may be handled by clients. // // The smtp package is frozen and is not accepting new features. // Some external packages provide more functionality. See: // // https://godoc.org/?q=smtp
package smtp import ( ) // A Client represents a client connection to an SMTP server. type Client struct { // Text is the textproto.Conn used by the Client. It is exported to allow for // clients to add extensions. Text *textproto.Conn // keep a reference to the connection so it can be used to create a TLS // connection later conn net.Conn // whether the Client is using TLS tls bool serverName string // map of supported extensions ext map[string]string // supported auth mechanisms auth []string localName string // the name to use in HELO/EHLO didHello bool // whether we've said HELO/EHLO helloError error // the error from the hello } // Dial returns a new [Client] connected to an SMTP server at addr. // The addr must include a port, as in "mail.example.com:smtp". func ( string) (*Client, error) { , := net.Dial("tcp", ) if != nil { return nil, } , , := net.SplitHostPort() return NewClient(, ) } // NewClient returns a new [Client] using an existing connection and host as a // server name to be used when authenticating. func ( net.Conn, string) (*Client, error) { := textproto.NewConn() , , := .ReadResponse(220) if != nil { .Close() return nil, } := &Client{Text: , conn: , serverName: , localName: "localhost"} _, .tls = .(*tls.Conn) return , nil } // Close closes the connection. func ( *Client) () error { return .Text.Close() } // hello runs a hello exchange if needed. func ( *Client) () error { if !.didHello { .didHello = true := .ehlo() if != nil { .helloError = .helo() } } return .helloError } // Hello sends a HELO or EHLO to the server as the given host name. // Calling this method is only necessary if the client needs control // over the host name used. The client will introduce itself as "localhost" // automatically otherwise. If Hello is called, it must be called before // any of the other methods. func ( *Client) ( string) error { if := validateLine(); != nil { return } if .didHello { return errors.New("smtp: Hello called after other methods") } .localName = return .hello() } // cmd is a convenience function that sends a command and returns the response func ( *Client) ( int, string, ...any) (int, string, error) { , := .Text.Cmd(, ...) if != nil { return 0, "", } .Text.StartResponse() defer .Text.EndResponse() , , := .Text.ReadResponse() return , , } // helo sends the HELO greeting to the server. It should be used only when the // server does not support ehlo. func ( *Client) () error { .ext = nil , , := .cmd(250, "HELO %s", .localName) return } // ehlo sends the EHLO (extended hello) greeting to the server. It // should be the preferred greeting for servers that support it. func ( *Client) () error { , , := .cmd(250, "EHLO %s", .localName) if != nil { return } := make(map[string]string) := strings.Split(, "\n") if len() > 1 { = [1:] for , := range { , , := strings.Cut(, " ") [] = } } if , := ["AUTH"]; { .auth = strings.Split(, " ") } .ext = return } // StartTLS sends the STARTTLS command and encrypts all further communication. // Only servers that advertise the STARTTLS extension support this function. func ( *Client) ( *tls.Config) error { if := .hello(); != nil { return } , , := .cmd(220, "STARTTLS") if != nil { return } .conn = tls.Client(.conn, ) .Text = textproto.NewConn(.conn) .tls = true return .ehlo() } // TLSConnectionState returns the client's TLS connection state. // The return values are their zero values if [Client.StartTLS] did // not succeed. func ( *Client) () ( tls.ConnectionState, bool) { , := .conn.(*tls.Conn) if ! { return } return .ConnectionState(), true } // Verify checks the validity of an email address on the server. // If Verify returns nil, the address is valid. A non-nil return // does not necessarily indicate an invalid address. Many servers // will not verify addresses for security reasons. func ( *Client) ( string) error { if := validateLine(); != nil { return } if := .hello(); != nil { return } , , := .cmd(250, "VRFY %s", ) return } // Auth authenticates a client using the provided authentication mechanism. // A failed authentication closes the connection. // Only servers that advertise the AUTH extension support this function. func ( *Client) ( Auth) error { if := .hello(); != nil { return } := base64.StdEncoding , , := .Start(&ServerInfo{.serverName, .tls, .auth}) if != nil { .Quit() return } := make([]byte, .EncodedLen(len())) .Encode(, ) , , := .cmd(0, strings.TrimSpace(fmt.Sprintf("AUTH %s %s", , ))) for == nil { var []byte switch { case 334: , = .DecodeString() case 235: // the last message isn't base64 because it isn't a challenge = []byte() default: = &textproto.Error{Code: , Msg: } } if == nil { , = .Next(, == 334) } if != nil { // abort the AUTH .cmd(501, "*") .Quit() break } if == nil { break } = make([]byte, .EncodedLen(len())) .Encode(, ) , , = .cmd(0, string()) } return } // Mail issues a MAIL command to the server using the provided email address. // If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME // parameter. If the server supports the SMTPUTF8 extension, Mail adds the // SMTPUTF8 parameter. // This initiates a mail transaction and is followed by one or more [Client.Rcpt] calls. func ( *Client) ( string) error { if := validateLine(); != nil { return } if := .hello(); != nil { return } := "MAIL FROM:<%s>" if .ext != nil { if , := .ext["8BITMIME"]; { += " BODY=8BITMIME" } if , := .ext["SMTPUTF8"]; { += " SMTPUTF8" } } , , := .cmd(250, , ) return } // Rcpt issues a RCPT command to the server using the provided email address. // A call to Rcpt must be preceded by a call to [Client.Mail] and may be followed by // a [Client.Data] call or another Rcpt call. func ( *Client) ( string) error { if := validateLine(); != nil { return } , , := .cmd(25, "RCPT TO:<%s>", ) return } type dataCloser struct { c *Client io.WriteCloser } func ( *dataCloser) () error { .WriteCloser.Close() , , := .c.Text.ReadResponse(250) return } // Data issues a DATA command to the server and returns a writer that // can be used to write the mail headers and body. The caller should // close the writer before calling any more methods on c. A call to // Data must be preceded by one or more calls to [Client.Rcpt]. func ( *Client) () (io.WriteCloser, error) { , , := .cmd(354, "DATA") if != nil { return nil, } return &dataCloser{, .Text.DotWriter()}, nil } var testHookStartTLS func(*tls.Config) // nil, except for tests // SendMail connects to the server at addr, switches to TLS if // possible, authenticates with the optional mechanism a if possible, // and then sends an email from address from, to addresses to, with // message msg. // The addr must include a port, as in "mail.example.com:smtp". // // The addresses in the to parameter are the SMTP RCPT addresses. // // The msg parameter should be an RFC 822-style email with headers // first, a blank line, and then the message body. The lines of msg // should be CRLF terminated. The msg headers should usually include // fields such as "From", "To", "Subject", and "Cc". Sending "Bcc" // messages is accomplished by including an email address in the to // parameter but not including it in the msg headers. // // The SendMail function and the net/smtp package are low-level // mechanisms and provide no support for DKIM signing, MIME // attachments (see the mime/multipart package), or other mail // functionality. Higher-level packages exist outside of the standard // library. func ( string, Auth, string, []string, []byte) error { if := validateLine(); != nil { return } for , := range { if := validateLine(); != nil { return } } , := Dial() if != nil { return } defer .Close() if = .hello(); != nil { return } if , := .Extension("STARTTLS"); { := &tls.Config{ServerName: .serverName} if testHookStartTLS != nil { testHookStartTLS() } if = .StartTLS(); != nil { return } } if != nil && .ext != nil { if , := .ext["AUTH"]; ! { return errors.New("smtp: server doesn't support AUTH") } if = .Auth(); != nil { return } } if = .Mail(); != nil { return } for , := range { if = .Rcpt(); != nil { return } } , := .Data() if != nil { return } _, = .Write() if != nil { return } = .Close() if != nil { return } return .Quit() } // Extension reports whether an extension is support by the server. // The extension name is case-insensitive. If the extension is supported, // Extension also returns a string that contains any parameters the // server specifies for the extension. func ( *Client) ( string) (bool, string) { if := .hello(); != nil { return false, "" } if .ext == nil { return false, "" } = strings.ToUpper() , := .ext[] return , } // Reset sends the RSET command to the server, aborting the current mail // transaction. func ( *Client) () error { if := .hello(); != nil { return } , , := .cmd(250, "RSET") return } // Noop sends the NOOP command to the server. It does nothing but check // that the connection to the server is okay. func ( *Client) () error { if := .hello(); != nil { return } , , := .cmd(250, "NOOP") return } // Quit sends the QUIT command and closes the connection to the server. func ( *Client) () error { if := .hello(); != nil { return } , , := .cmd(221, "QUIT") if != nil { return } return .Text.Close() } // validateLine checks to see if a line has CR or LF as per RFC 5321. func validateLine( string) error { if strings.ContainsAny(, "\n\r") { return errors.New("smtp: A line must not contain CR or LF") } return nil }