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()
}
@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"

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