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