-
-
Save chrisgillis/10888032 to your computer and use it in GitHub Desktop.
package main | |
import ( | |
"fmt" | |
"log" | |
"net" | |
"net/mail" | |
"net/smtp" | |
"crypto/tls" | |
) | |
// SSL/TLS Email Example | |
func main() { | |
from := mail.Address{"", "[email protected]"} | |
to := mail.Address{"", "[email protected]"} | |
subj := "This is the email subject" | |
body := "This is an example body.\n With two lines." | |
// Setup headers | |
headers := make(map[string]string) | |
headers["From"] = from.String() | |
headers["To"] = to.String() | |
headers["Subject"] = subj | |
// Setup message | |
message := "" | |
for k,v := range headers { | |
message += fmt.Sprintf("%s: %s\r\n", k, v) | |
} | |
message += "\r\n" + body | |
// Connect to the SMTP Server | |
servername := "smtp.example.tld:465" | |
host, _, _ := net.SplitHostPort(servername) | |
auth := smtp.PlainAuth("","[email protected]", "password", host) | |
// TLS config | |
tlsconfig := &tls.Config { | |
InsecureSkipVerify: true, | |
ServerName: host, | |
} | |
// Here is the key, you need to call tls.Dial instead of smtp.Dial | |
// for smtp servers running on 465 that require an ssl connection | |
// from the very beginning (no starttls) | |
conn, err := tls.Dial("tcp", servername, tlsconfig) | |
if err != nil { | |
log.Panic(err) | |
} | |
c, err := smtp.NewClient(conn, host) | |
if err != nil { | |
log.Panic(err) | |
} | |
// Auth | |
if err = c.Auth(auth); err != nil { | |
log.Panic(err) | |
} | |
// To && From | |
if err = c.Mail(from.Address); err != nil { | |
log.Panic(err) | |
} | |
if err = c.Rcpt(to.Address); err != nil { | |
log.Panic(err) | |
} | |
// Data | |
w, err := c.Data() | |
if err != nil { | |
log.Panic(err) | |
} | |
_, err = w.Write([]byte(message)) | |
if err != nil { | |
log.Panic(err) | |
} | |
err = w.Close() | |
if err != nil { | |
log.Panic(err) | |
} | |
c.Quit() | |
} |
The libexec/src/net/smtp/smtp.go has similar code, in the func SendMail.
To the people getting "first record does not look like a TLS handshake" on port 587: use port 465 instead, if your provider supports it.
Port 465 is TLS-only, which means: the client connects and immediately establishes a TLS handshake. Just like with HTTPS. This kind of connection is often incorrectly named "SSL" by email providers.
Port 587 is a cleartext SMTP port. This means that the client connects, introduces itself in plaintext, then sends a STARTTLS
command and TLS handshake happens. This kind of connection is incorrectly named "TLS" by email providers.
If your provider doesn't support "SSL" connections (port 465), you're going to need to use another provider (or find an example using the StartTLS function)
For those still getting this issue
I've found https://github.com/emersion/go-smtp makes solving this problem a whole lot easier. You can assign TLS config like so:
d.TLSConfig = &tls.Config{
InsecureSkipVerify: false,
ServerName: smtpHost,
RootCAs: rootCAs,
}
For Gmail: go to https://myaccount.google.com/apppasswords , create an app password and use it instead of your normal password. This should work for everyone, including 2FA accounts.
Thank you for shaing!
You express internal principle of smtp so clearly. Thank you.
Awesome! It works perfectly with Daum
mail under SSL.
Thank you for sharing your code.
This is a wonderful example you've provided. (Thank you very much!) However, it doesn't work. I get the following error message:
2014/08/16 07:44:45 tls: first record does not look like a TLS handshake
panic: tls: first record does not look like a TLS handshakegoroutine 16 [running]:
runtime.panic(0x18d660, 0xc2080005a0)
/usr/local/go/src/pkg/runtime/panic.c:279 +0xf5
log.Panic(0x671e30, 0x1, 0x1)
/usr/local/go/src/pkg/log/log.go:307 +0xb6
main.main()
/Users/richardeng/fred.go:52 +0x7a9Line 52, as in your example, refers to the tls.Dial() call. What's wrong??
(FYI, the servername I'm using is "smtp.gmail.com:587".)
Please verify if you are using correct port
Please note, this example is prone to man-in-the-middle attacks. InsecureSkipVerify
must be set to false,
otherwise the server certificate is not being verified. Thus, a mitm attacker can just present any (e.g. self-signed) certificate and the client will silently accept and connect. You don't want that!
See also https://golang.org/pkg/crypto/tls/#Config
You should also add the date header, in order to be conform with RFC 5322:
headers["Date"] = time.Now().Format("Mon, 02 Jan 2006 15:04:05 -0700")
Thanks for the example!
thanks a lot @chrisgillis & @cddmp
Works perfectly, thanks for sharing
You can lose the annoying Go Vet warnings about composite literal using unkeyed fields by:-
from := mail.Address{Name:"", Address:"[email protected]"}
to := mail.Address{Name:"", Address:"[email protected]"}
This worked for me. Thanks a lot
Awesome, thanks for this!
This indeed works.
Appreaciated.
Generational answer 🫡
Passed on from debugger to debugger
Work well.
Works great!
Thanks a lot
This worked great for fastmail.com STMP. My tls config didn't require the insecure verify as well:
// TLS config
tlsconfig := &tls.Config{
ServerName: host,
}
In case someone finds it useful, to send a HTML body, add the following headers:
body := "<html><body><h1>Hello World!</h1></body></html>"
headers["MIME-Version"] = "1.0"
headers["Content-Type"] = "text/html; charset=UTF-8"
// Setup message
message := ""
for k,v := range headers {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + body
Should we make the protection for SMTP-header-injection more explicit? like:
https://www.geeksforgeeks.org/what-is-smtp-header-injection/
https://github.com/golang/go/blob/87ec2c959c73e62bfae230ef7efca11ec2a90804/src/net/smtp/smtp.go#L429
// Setup message
message := ""
for k, v := range headers {
line := fmt.Sprintf("%s: %s", k, v)
// security assurance: RFC 5321
if err := validateLine(line); err != nil {
return err
}
message += line + "\r\n"
}
message += "\r\n" + body
// validateLine checks to see if a line has CR or LF as per RFC 5321.
func validateLine(line string) error {
if strings.ContainsAny(line, "\n\r") {
return errors.New("[mailer] smtp: A line must not contain CR or LF")
}
return nil
}
To the people getting "first record does not look like a TLS handshake" on port 587: use port 465 instead, if your provider supports it.
Your suggestion worked for me! Thanks bro! Also thanks to @chrisgillis
Referred to this code for sending Yahoo! mail: smtp.mail.yahoo.com:465
Worked as intended. Thanks!
If you get this error:
(#MBR1212) Incorrect username or password.
Make sure you toggle this Account Security option in your Yahoo! account settings:
Allow apps that use less secure sign in