Email functionality is a crucial component in enterprise applications. Azure Communication Services (ACS) provides SMTP Relay service, making it easy to integrate email sending capabilities. However, during implementation, we encountered some authentication-related challenges. In this article, I'll share our journey of discovering and resolving these issues.
Prerequisites
Before diving in, ensure you have:
- An Azure subscription
- Email Communication Services resource
- Communication Services resource
- Microsoft Entra ID application
- Role assignment in Communication Services (Email Sender role for your application)
The role requires these permissions:
Microsoft.Communication/CommunicationServices Read
Microsoft.Communication/CommunicationServices Write
Microsoft.Communication/EmailServices Write
The Initial Challenge
We started with Go's standard library SMTP client implementation. Here's our initial code:
func main() {
smtpHost := "smtp.azurecomm.net:587"
host, _, err := net.SplitHostPort(smtpHost)
if err != nil {
fmt.Println("Failed to split host and port:", err)
return
}
fmt.Println("host:", host)
e := email.NewEmail()
e.From = fmt.Sprintf("%s <%s>", "From", "DoNotReply@xxxx.azurecomm.net")
e.To = []string{"{to_address}"}
e.Subject = "Test Email"
e.Text = []byte("This is an email sent using github.com/jordan-wright/email.")
// username format: communicationService.applicationId.tenantId
auth := smtp.PlainAuth("", "{username}", "{password}", host)
tlsConfig := &tls.Config{
ServerName: host,
}
err = e.SendWithStartTLS(smtpHost, auth, tlsConfig)
if err != nil {
fmt.Println("Failed to send email:", err)
} else {
fmt.Println("Email sent successfully!")
}
}
However, this resulted in an error:
Failed to authenticate: 504 5.7.4 Unrecognized authentication type
Deep Dive into the Problem
To better understand the issue, we used OpenSSL to directly communicate with the SMTP server. This approach allowed us to see exactly what authentication methods the server supports.
Note: Telnet isn't suitable here as it doesn't support STARTTLS encryption.
$ openssl s_client -starttls smtp -ign_eof -crlf -connect smtp.azurecomm.net:587
ehlo localhost
250-ic3-transport-smtp-acs-deployment-67d47c5c4c-kdkxc Hello [14.19.xx.xx]
250-SIZE 31457280
250 AUTH LOGIN
The server's response revealed something crucial: ACS SMTP server only supports AUTH LOGIN
authentication. When we tried AUTH PLAIN
:
AUTH PLAIN
504 5.7.4 Unrecognized authentication type
This explained our initial error - Go's smtp.PlainAuth
uses AUTH PLAIN
, which isn't supported by the server.
The Solution
Understanding the root cause, we implemented a custom authenticator supporting AUTH LOGIN
:
type loginAuth struct {
username string
password string
}
// LoginAuth implements smtp.Auth interface
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, fmt.Errorf("unknown fromServer: %s", string(fromServer))
}
}
return nil, nil
}
The complete implementation looks like this:
func main() {
smtpHost := "smtp.azurecomm.net:587"
host, _, err := net.SplitHostPort(smtpHost)
if err != nil {
fmt.Println("Failed to split host and port:", err)
return
}
e := email.NewEmail()
e.From = fmt.Sprintf("%s <%s>", "From", "DoNotReply@xxxx.azurecomm.net")
e.To = []string{"{to_address}"}
e.Subject = "Test Email"
e.Text = []byte("This is an email sent using github.com/jordan-wright/email.")
auth := LoginAuth(
// username format: communicationService.applicationId.tenantId
"{username}",
"{password}",
)
tlsConfig := &tls.Config{
ServerName: host,
}
err = e.SendWithStartTLS(smtpHost, auth, tlsConfig)
if err != nil {
fmt.Println("Failed to send email:", err)
} else {
fmt.Println("Email sent successfully!")
}
}
Key Takeaways
- Azure Communication Services SMTP only supports
AUTH LOGIN
authentication - Go's standard library doesn't support
AUTH LOGIN
out of the box - When troubleshooting SMTP issues, OpenSSL is an invaluable tool for direct server communication
- Always verify the supported authentication methods when integrating with email services
Additional Resources
- Azure Communication Services SMTP Authentication Documentation
- Related Go Issue
- Python's smtplib Implementation
If you found this article helpful, feel free to clap and follow for more technical content about cloud services and Go programming. And i will write my notes on ashing's notes
Top comments (0)