Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@xeoncross
Last active November 5, 2023 10:58
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save xeoncross/9a5ffa2e4edc2be7681a41f57d1e5c51 to your computer and use it in GitHub Desktop.
Save xeoncross/9a5ffa2e4edc2be7681a41f57d1e5c51 to your computer and use it in GitHub Desktop.
Simple SMTP mail sender in golang that can send email directly to anyone. Super basic, but great for sending notice emails. Upgrades to STARTTLS if it can.
package main
import (
"encoding/base64"
"fmt"
"log"
"net"
"net/mail"
"net/smtp"
"strings"
"time"
)
// telnet gmail-smtp-in.l.google.com 25
// openssl s_client -connect gmail-smtp-in.l.google.com:25
func main() {
var (
mx string
err error
)
// Connect to the server, authenticate, set the sender and recipient,
// and send the email all in one step.
from := "me@example.com"
to := "bob@gmail.com"
subject := "Test Email"
body := "This is the email body at " + time.Now().String()
msg := composeMimeMail(to, from, subject, body)
mx, err = getMXRecord(to)
if err != nil {
log.Fatal(err)
}
err = smtp.SendMail(mx+":25", nil, from, []string{to}, msg)
if err != nil {
log.Fatal(err)
}
}
func getMXRecord(to string) (mx string, err error) {
var e *mail.Address
e, err = mail.ParseAddress(to)
if err != nil {
return
}
domain := strings.Split(e.Address, "@")[1]
var mxs []*net.MX
mxs, err = net.LookupMX(domain)
if err != nil {
return
}
for _, x := range mxs {
mx = x.Host
return
}
return
}
// Never fails, tries to format the address if possible
func formatEmailAddress(addr string) string {
e, err := mail.ParseAddress(addr)
if err != nil {
return addr
}
return e.String()
}
func encodeRFC2047(str string) string {
// use mail's rfc2047 to encode any string
addr := mail.Address{Address: str}
return strings.Trim(addr.String(), " <>")
}
func composeMimeMail(to string, from string, subject string, body string) []byte {
header := make(map[string]string)
header["From"] = formatEmailAddress(from)
header["To"] = formatEmailAddress(to)
header["Subject"] = encodeRFC2047(subject)
header["MIME-Version"] = "1.0"
header["Content-Type"] = "text/plain; charset=\"utf-8\""
header["Content-Transfer-Encoding"] = "base64"
message := ""
for k, v := range header {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + base64.StdEncoding.EncodeToString([]byte(body))
return []byte(message)
}
@xeoncross
Copy link
Author

xeoncross commented Dec 17, 2016

Test your SMTP connection to make sure your HOST/ISP isn't blocking SMTP

openssl s_client -connect gmail-smtp-in.l.google.com:25

@xeoncross
Copy link
Author

Simple example of doing the SMTP dance yourself.

@MarlikAlmighty
Copy link

@MarlikAlmighty
Copy link

@xeoncross
Copy link
Author

Recently stumbled across this and want to point out you can use a net.Resolver to set the context for proper timeouts.

func LookupMX(ctx context.Context, domain string) ([]*net.MX, error) {
	var r net.Resolver
	return r.LookupMX(ctx, domain)
}

@metaleap
Copy link

metaleap commented Nov 5, 2023

Wondering: in composeMimeMail you pass subject to encodeRFC2047 which interprets its arg as an email address. What's the idea?

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