Password Reset Feature: SMTP Debugging

bitorsic

Yash Jaiswal

Posted on October 2, 2024

Password Reset Feature: SMTP Debugging

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:

  1. Remove the call to SMTPConnect() function from the init() function in main.go. Now the init() function is:
func init() {
    godotenv.Load()

    config.DBConnect()
    config.RedisConnect()
}
Enter fullscreen mode Exit fullscreen mode
  1. Call the SMTPConnect() function at the start of the SendOTP function, so that the SMTP connection is initialized each time an email has to be sent
  2. Once the email is done being written, close the SMTP connection by calling close() on the SMTPClient

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
}
Enter fullscreen mode Exit fullscreen mode

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

💖 💪 🙅 🚩
bitorsic
Yash Jaiswal

Posted on October 2, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related