Skip to content

Instantly share code, notes, and snippets.

@chrisgillis
Created April 16, 2014 14:48
Show Gist options
  • Save chrisgillis/10888032 to your computer and use it in GitHub Desktop.
Save chrisgillis/10888032 to your computer and use it in GitHub Desktop.
Golang SSL SMTP Example
package main
import (
"fmt"
"log"
"net"
"net/mail"
"net/smtp"
"crypto/tls"
)
// SSL/TLS Email Example
func main() {
from := mail.Address{"", "username@example.tld"}
to := mail.Address{"", "username@anotherexample.tld"}
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("","username@example.tld", "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()
}
@DisaTemp
Copy link

How would you integrate attachments into this?

@picasso250
Copy link

It works for QQ mail, and thanks very very much!

@helgigit
Copy link

Thanks! Works for smtp.zoho.com

@myafeier
Copy link

Thanks Works for smtp.mxhichina.com

@cruiz1391
Copy link

I am getting "Msg": "first record does not look like a TLS handshake", using office365 (smtp.office365.com:587)

@emacsist
Copy link

Nice

@paulburlumi
Copy link

@cruiz1391 See the discussion here for a solution using office365
mattermost/mattermost#954
golang/go#9899
Using the workaround for AUTH LOGIN worked for me.

@JIElite
Copy link

JIElite commented Feb 2, 2017

Thank you for sharing
It do works for AWS SES!

just modify

  1. servername: endpoint, like: email-smtp.us-west-2.amazonaws.com:465
  2. smtp.PlainAuth username: IAM username
  3. smtp.PlainAuth password: IAM password

Thank you for shaing!

@JoelTrottier
Copy link

JoelTrottier commented Aug 30, 2017

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

@jumping
Copy link

jumping commented Mar 29, 2018

The libexec/src/net/smtp/smtp.go has similar code, in the func SendMail.

@gjedeer
Copy link

gjedeer commented Apr 12, 2018

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)

@maxtuzz
Copy link

maxtuzz commented Jun 28, 2018

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,
	}

@selfisekai
Copy link

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.

@wjwangji
Copy link

Thank you for shaing!

@yu-chenxi
Copy link

You express internal principle of smtp so clearly. Thank you.

@mununki
Copy link

mununki commented Jun 28, 2019

Awesome! It works perfectly with Daum mail under SSL.
Thank you for sharing your code.

@asheikm
Copy link

asheikm commented Apr 30, 2020

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 handshake

goroutine 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 +0x7a9

Line 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

@cddmp
Copy link

cddmp commented Jan 5, 2021

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!

@demon36
Copy link

demon36 commented Jan 10, 2021

thanks a lot @chrisgillis & @cddmp

@saphena
Copy link

saphena commented Apr 22, 2021

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:"username@example.tld"}
to := mail.Address{Name:"", Address:"username@anotherexample.tld"}

@MArK1done
Copy link

This worked for me. Thanks a lot

@MatthewMarkgraaff
Copy link

Awesome, thanks for this!

@afa7789
Copy link

afa7789 commented Apr 5, 2022

This indeed works.
Appreaciated.

@yngfoxx
Copy link

yngfoxx commented Nov 2, 2022

Generational answer 🫡
Passed on from debugger to debugger

@didate
Copy link

didate commented Mar 1, 2023

Work well.

@denglitong
Copy link

Works great!

@SantiiRepair
Copy link

Thanks a lot

@amitsaha
Copy link

amitsaha commented Sep 5, 2023

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"

@fritx
Copy link

fritx commented Jul 9, 2024

// 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
}

@AhmedBHameed
Copy link

AhmedBHameed commented Jul 27, 2024

@gjedeer

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment