Next.js Discord

Discord Forum

Workaround for yet another nodemailer not working in production issue

Answered
Mugger Crocodile posted this in #help-forum
Open in Discord
Mugger CrocodileOP
Hi folks.

So I can see this is a recurring one, and none of the few answers are working. I get an erratic, puzzling behaviour, sometimes getting back different -but related- error messages in my sendMail.catch method, like Error: Unexpected socket close, Error: Greeting never received and Error: Client network socket disconnected before secure TLS connection was established.

It's a true shame that something as basic be that faulty, and kind of a dealbreaker for my requirements. I cannot spare reliable emailing.

The rest of the diagnosis is pretty similar to that of other posts: works perfectly in dev, I'm using gmail's 16-digit app password, port 465 and secure tls connection, and even getting confirmation from the transporter.verify method. Frustration is intense.

If anyone has workedaround or fixed this issue, help would be most appreciated.

My transporter setup is as follows:

const transporter = nodemailer.createTransport({
      service: "gmail",
      host: 'smtp.gmail.com',
      port: 465,
      secure: true,
      // secureConnection: true,
      auth: {
        user: process.env.GMAIL_USER,
        pass: process.env.GMAIL_PASSWORD,
      },
    });


Then my trigger:
export const POST = async (req: Request, res: Response) => {
  const b = await req.json()
  transporter.sendMail(mailOptions(b.mail, b.name))
      .then(r => {
        bot.sendMessage(process.env.TG_CHAT_ID!, `@sendMessage then`)
      })
      .catch(r => {
        bot.sendMessage(process.env.TG_CHAT_ID!, `@sendMessage catch ${r}`)
      })
      .finally(() => {
        bot.sendMessage(process.env.TG_CHAT_ID!, `@sendMessage finally`) 
      })
}


In my tg channel, sometimes I get the then branch, others the catch with some of the error messages I posted at the begginng, usually I see the finally, but sometimes none of them.

The mail never goes out.

As stated, it does just fine in dev.

Thanks
Answered by Marchy
The error seems to be related to a websocket issue? There's an example here - Do you have the transporter defined inside the route handler?

https://javascript.plainenglish.io/sending-emails-with-nodemailer-in-next-js-ccada06abfc9
View full answer

6 Replies

Mugger CrocodileOP
Well, Ii moved on to sendgrid, and now I'm getting

Error: socket hang up
and
Error: Client network socket disconnected before secure TLS connection was established


I'm about to cry
Mugger CrocodileOP
This is my code as of now:

import sgMail from '@sendgrid/mail'
import { readFileSync } from 'fs'
import path from 'path'

sgMail.setApiKey(process.env.SENDGRID_KEY!)

const libDirectory = path.resolve(process.cwd(), "src/app/lib")
const f1 = readFileSync( path.join(libDirectory, "myFile1.pdf") )
const f2 = readFileSync( path.join(libDirectory, "myFile2.pdf") )

export const sendMailWithAttachments = (mail: string, name: string) => {
  const msg = {
    to: mail,
    from: `MyName<${process.env.GMAIL_USER}>`,
    subject: `${nombre}, here are your files`,
    text: `Big text`,
    attachments: [{
      content: f1.toString('base64'),
      filename: 'MyFile1Name.pdf',
      type: 'pdf'
    },{
      content: vidriera.toString('base64'),
      filename: 'MyFileName2.pdf',
      type: 'pdf'
    }
  ]
  }
  return sgMail.send(msg) // This returns a promise
}


And in my route.ts

import { NextResponse } from "next/server";
import bot from "../lib/tg";
import { sendMailWithAttachments } from "../lib/sendgrid";

export const POST = async (req: Request, res: Response) => {
  const b = await req.json()
  if (b.password == process.env.TRIGGER_PASSWORD) {
    console.log(`Sending email...`)
    bot.sendMessage(process.env.TG_CHAT_ID!, `Sending mails and pdfs to ${b.name} (${b.mail})...`)
    sendMailWithAttachments(b.mail, b.name)
      .then(
        r => {
          console.log(`...mail sent!`)
          bot.sendMessage(process.env.TG_CHAT_ID!, `...sent to ${b.name} (${b.mail})! ${r}`)
        })
      .catch(e => {
        console.log(`...mail failed!`)
        bot.sendMessage(process.env.TG_CHAT_ID!, `...mail failed to ${b.name} (${b.mail})! ${e}`)
      })
    return NextResponse.json({ ok: true })
  } else {
    return NextResponse.json({ ok: false, msg: `Unauthorized c:` })
  }
}


Still works like a charm in dev, blows up in vercel
Mugger CrocodileOP
It's been fixed by changing the endpoint function to
export const POST = async (req: Request, res: Response) => {
  const b = await req.json()
  if (b.password == process.env.TRIGGER_PASSWORD) {
    bot.sendMessage(process.env.TG_CHAT_ID!, `Sending mails and pdfs to ${b.nombre} (${b.mail})...`)
    try{
      const r = await sendMailWithAttachments(b.mail, b.nombre)
      bot.sendMessage(process.env.TG_CHAT_ID!, `...sent to ${b.nombre} (${b.mail})! ${r}`)
    }catch(e){
      bot.sendMessage(process.env.TG_CHAT_ID!, `...failed for ${b.name} (${b.mail})! ${e}`)
    }
    return NextResponse.json({ ok: true })
  } else {
    return NextResponse.json({ ok: false, msg: `No autorizado c:` })
  }

I might just cry anyway
The error seems to be related to a websocket issue? There's an example here - Do you have the transporter defined inside the route handler?

https://javascript.plainenglish.io/sending-emails-with-nodemailer-in-next-js-ccada06abfc9
Answer
Mugger CrocodileOP
My goodness, that worked
transporter = nodemailer.createTransport({
      service: "gmail",
      // host: 'smtp.gmail.com',
      // port: 465,
      // secure: true,
      auth: {
        user: process.env.GMAIL_USER,
        pass: process.env.GMAIL_PASSWORD,
      },
    });
@Marchy The error seems to be related to a websocket issue? There's an example here - Do you have the transporter defined inside the route handler? https://javascript.plainenglish.io/sending-emails-with-nodemailer-in-next-js-ccada06abfc9
Mugger CrocodileOP
No, my transporter is setup in a different module, from which I export an instance of an Emailer class, an attribute of which points to the transporter

import { emailer } from '../lib/mailer'

export const POST = async (req: Request, res: Response) => {
  ...
 emailer.sendMail('user@gmail.com', 'Name').
  ...
}


export class Emailer {
  private readonly transporter: nodemailer.Transporter;

  constructor() {
    this.transporter = nodemailer.createTransport({
      service: "gmail",
      // host: 'smtp.gmail.com',
      // port: 465,
      // secure: true,
      // secureConnection: true,
      auth: {
        user: process.env.GMAIL_USER,
        pass: process.env.GMAIL_PASSWORD,
      },
    });

   sendMail(email, username){ 
     ...
     return this.transporter.sendMail(mailOptions);
  }
}