Reputation: 1311
I'm trying to create a simple Go emailing service using the default Go packages net/smtp - I know there's gomailer, but i'd like to use the standard library
I need help with configuring the tls/server setting to work with Office365
I believe that I have the correct host:
smtp.office365.com:587
From copying the documentation for smtp that Microsoft provide, however, I get the following error in my console when running the below code:
Error: tls: first record does not look like a TLS handshake panic: runtime error: invalid memory address or nil pointer dereference
package main
import (
"fmt"
"net"
mail "net/mail"
smtp "net/smtp"
)
func main() {
from := mail.Address{"", "[email protected]"}
to := mail.Address{"", "[email protected]"}
subject := "My test subject"
body := "Test email body"
// Setup email headers
headers := make(map[string]string)
headers["From"] = from.String()
headers["To"] = to.String()
headers["Subject"] = subject
message := ""
for k, v := range headers {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + body
servername := "smtp.office365.com:587"
host, _, _ := net.SplitHostPort(servername)
auth := smtp.PlainAuth("", "[email protected]", "password", host)
tlsconfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: host,
}
conn, err := tls.Dial("tcp", "smtp.office365.com:587", tlsconfig)
if err != nil {
fmt.Println("tls.Dial Error: %s", err)
}
c, err := smtp.NewClient(conn, host)
if err != nil {
fmt.Println("smtp.NewClient Error: %s", err)
}
if err = c.Auth(auth); err != nil {
fmt.Println("c.Auth Error: %s", err)
}
if err = c.Mail(from.Address); err != nil {
fmt.Println("c.Mail Error: %s", err)
}
if err = c.Rcpt(to.Address); err != nil {
fmt.Println("c.Rcpt Error: %s", err)
}
w, err := c.Data()
if err != nil {
fmt.Println("c.Data Error: %s", err)
}
_, err = w.Write([]byte(message))
if err != nil {
fmt.Println("Error: %s", err)
}
err = w.Close()
if err != nil {
fmt.Println("reader Error: %s", err)
}
c.Quit()
}
Any examples of an O365 client will be appreciated, or anything that anyone can spot that seems suspect will be great
Thanks
Upvotes: 3
Views: 5113
Reputation: 24988
Below worked fine with me:
package main
import (
"bytes"
"crypto/tls"
"errors"
"fmt"
"net"
"net/smtp"
"text/template"
)
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte(a.username), nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("Unknown from server")
}
}
return nil, nil
}
func main() {
// Sender data.
from := "O365 logging name"
password := "O365 logging pasword"
// Receiver email address.
to := []string{
"receiver email",
}
// smtp server configuration.
smtpHost := "smtp.office365.com"
smtpPort := "587"
conn, err := net.Dial("tcp", "smtp.office365.com:587")
if err != nil {
println(err)
}
c, err := smtp.NewClient(conn, smtpHost)
if err != nil {
println(err)
}
tlsconfig := &tls.Config{
ServerName: smtpHost,
}
if err = c.StartTLS(tlsconfig); err != nil {
println(err)
}
auth := LoginAuth(from, password)
if err = c.Auth(auth); err != nil {
println(err)
}
t, _ := template.ParseFiles("template.html")
var body bytes.Buffer
mimeHeaders := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
body.Write([]byte(fmt.Sprintf("Subject: This is a test subject \n%s\n\n", mimeHeaders)))
t.Execute(&body, struct {
Name string
Message string
}{
Name: "Hasan Yousef",
Message: "This is a test message in a HTML template",
})
// Sending email.
err = smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, body.Bytes())
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Email Sent!")
}
With the below template as bonus :)
<!-- template.html -->
<!DOCTYPE html>
<html>
<body>
<h3>Name:</h3><span>{{.Name}}</span><br/><br/>
<h3>Email:</h3><span>{{.Message}}</span><br/>
</body>
</html>
Upvotes: 1
Reputation: 41
The following codes implement AUTH LOGIN
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte(a.username), nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("Unknown from server")
}
}
return nil, nil
}
tlsconfig := &tls.Config {
ServerName: host,
}
conn, err := net.Dial("tcp", "smtp.office365.com:587")
if err != nil {
return err
}
c, err := smtp.NewClient(conn, host)
if err != nil {
return err
}
if err = c.StartTLS(tlsconfig); err != nil {
return err
}
auth := LoginAuth(fromAddress, password)
if err = c.Auth(auth); err != nil {
return err
}
Upvotes: 4
Reputation: 1311
So the issue was all about authorisation. Firstly requiring that I use the StartTLS method on the client, and also that I write a function and methods to support LOGIN, something that the standard Go library doesn't support (for whatever reason)
See the functions and struct above the main()
Here's the full code, with the helper function, that can now successfully send an email through my O365 account:
package main
import (
"fmt"
"net"
"errors"
mail "net/mail"
smtp "net/smtp"
)
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte{}, nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("Unknown fromServer")
}
}
return nil, nil
}
func main() {
from := mail.Address{"", "[email protected]"}
to := mail.Address{"", "[email protected]"}
subject := "My test subject"
body := "Test email body"
headers := make(map[string]string)
headers["From"] = from.String()
headers["To"] = to.String()
headers["Subject"] = subject
message := ""
for k, v := range headers {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + body
tlsconfig := &tls.Config{
ServerName: host,
}
conn, err := tls.Dial("tcp", "smtp.office365.com:587", tlsconfig)
if err != nil {
fmt.Println("tls.Dial Error: ", err)
}
c, err := smtp.NewClient(conn, host)
if err != nil {
fmt.Println("smtp.NewClient Error: ", err)
}
if err = c.Auth(LoginAuth("[email protected]", "password")); err != nil {
fmt.Println("c.Auth Error: ", err)
return
}
if err = c.Mail(from.Address); err != nil {
fmt.Println("c.Mail Error: ", err)
}
if err = c.Rcpt(to.Address); err != nil {
fmt.Println("c.Rcpt Error: ", err)
}
w, err := c.Data()
if err != nil {
fmt.Println("c.Data Error: ", err)
}
_, err = w.Write([]byte(message))
if err != nil {
fmt.Println("Error: ", err)
}
err = w.Close()
if err != nil {
fmt.Println("reader Error: ", err)
}
c.Quit()
}
Upvotes: 1
Reputation: 4859
The error message Error: tls: first record does not look like a TLS handshake
is telling you what the problem is :-). If you try connecting to the server, you will see that (as any SMTP servers) it uses plain text:
telnet smtp.office365.com 587
Trying 2603:1026:c0b:10::2...
Connected to zrh-efz.ms-acdc.office.com.
Escape character is '^]'.
220 ZRAP278CA0003.outlook.office365.com Microsoft ESMTP MAIL Service ready at Mon, 11 Nov 2019 17:13:50 +0000
...
You need to use the STARTTLS
command, see https://en.wikipedia.org/wiki/Opportunistic_TLS (and the RFCs pointed by that wiki page).
In Go, it is https://golang.org/pkg/net/smtp/#Client.StartTLS.
In your code I noticed
tlsconfig := &tls.Config{
InsecureSkipVerify: true, <== REMOVE THIS
ServerName: host,
}
Please remove the InsecureSkipVerify
, it is, as the name implies, insecure and has nothing to do with the error you are facing.
Upvotes: 3