The Bug
I spun up the backend and frontend for the app, tested all the components, navigating through the app. I left the password reset feature for the last. When I entered my email address to send the OTP, I received the following error as an alert in the web browser:
write tcp 192.168.48.210:43244->103.103.197.223:587: write: broken pipe
The bug could be reproduced every time I tried sending an OTP.
Debugging
With the help of ChatGPT and some research, I understood the following:
The server of my app is trying to write
to a tcp
connection. The server is trying to write (email) from the port 43244
on my machine (private IP address 192.168.48.210
), to the SMTP server of ZohoMail (IP address 103.103.197.223
, port no. 587
). The write failed (write:broken pipe
), because it was trying to write to TCP connection that has been already closed.
I discovered that the SMTP server will close any TCP connections if it is idling for too long.
What I was doing before-
The function SMTPConnect()
from config/smtpConnection.go
performs dialing up and authentication all on a tcp connection to the SMTP server. I called this function while initializing the server app.
What happened is, when I was using the SMTPClient
after initialization, the app was trying to write to the TCP connection which was created when the app started. This TCP connection timed out as it was sitting idle when there was nothing to send over it, hence giving me the error.
What I needed to do-
Instead of calling the SMTPConnect()
function right at the start when the app is initialized, I need to open a TCP connection each time before sending an email, and close it right after, so as to avoid it timing out.
Here are the steps I took to do this:
- Remove the call to
SMTPConnect()
function from theinit()
function inmain.go
. Now theinit()
function is:
func init() {
godotenv.Load()
config.DBConnect()
config.RedisConnect()
}
- Call the
SMTPConnect()
function at the start of theSendOTP
function, so that the SMTP connection is initialized each time an email has to be sent - Once the email is done being written, close the SMTP connection by calling
close()
on theSMTPClient
Here's the final code for the SendOTP
function after making the changes:
func SendOTP(otp string, recipient string) error {
// connecting here because connection tends to time out
config.SMTPConnect()
sender := os.Getenv("SMTP_EMAIL")
client := config.SMTPClient
// setting the sender
err := client.Mail(sender)
if err != nil {
return err
}
// set recipient
err = client.Rcpt(recipient)
if err != nil {
return err
}
// start writing email
writeCloser, err := client.Data()
if err != nil {
return err
}
// contents of the email
msg := fmt.Sprintf(emailTemplate, recipient, otp)
// write the email
_, err = writeCloser.Write([]byte(msg))
if err != nil {
return err
}
// close and send email
err = writeCloser.Close()
if err != nil {
return err
}
// close smtp connection
err = client.Close()
if err != nil {
return err
}
return nil
}
Now, the bug has been fixed and the emails are being sent as they should be.
The feature is now completed. Thank you for reading.
The app is deployed on Render, here
The source code is in the GitHub Repository
Top comments (0)