Daniel Ioanitescu
Daniel Ioanitescu

Reputation: 387

Nodemailer in vercel not sending email in production

I'm using Nodemailer to send emails in my serverless Next.js project, deployed in Vercel, which works perfectly in development mode. But I'm having problems in production. No error returned, everything works the same way as is development mode, except I don't receive any email.

I have another project built with React and deployed in Heroku where I send emails the same way and it works fine, development and production, so I understand the problem is with Vercel.

Yes, I enabled "Allow Less Secured Apps" in Google account and yes, I enabled Captcha.

I also read this https://vercel.com/docs/solutions/email but it doesn't really make me understand what I should do in my case. I can see it's a matter of SMTP but I don't know what exactly.

Anybody experienced this kind of problem? How can I fix this?

const transporter = nodemailer.createTransport({
    host: "smtp.gmail.com",
    port: 465,
    auth: {
        user: [email protected],
        pass: myEmailPass
    }
});
            
const mailOptions = {
    from: `${req.body.name} ${req.body.email}`,
    to: [email protected],
    subject: `${req.body.subject}`,
    text: `Text: ${req.body.text}`
}
            
transporter.sendMail(mailOptions, (err, res) => {
    if(err) {
          console.log(err);
    } else {
          console.log("success");
    }
});

UPDATE

I changed to SendGrid: made an account, created an API Key, and changed the code like so(instead the one above):

sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
    to: `[email protected]`,
    from: `[email protected]`,
    subject: `${req.body.subject}`,
    text: `${req.body.text}`
};
sgMail
.send(msg)
.then(() => {
     console.log('email sent')
})
.catch((error) => {
     console.error("error", error)
});

It logs out "email sent" but I don't receive any email. It's the same problem like with Nodemailer. I'm confused now...

Upvotes: 24

Views: 24229

Answers (13)

Emmanuel Ngufor
Emmanuel Ngufor

Reputation: 1

Make sure you visit your deployment page for your project on Vercel and update your environmental variables.

Since you are using nodemailer on the backend, Vercel is not aware of any variables in in the .env file. If you have anu environmental variables in the .env file, you can upload the file it self on vercel and it would generate the configurations for your project:

  1. Go to your projects home page on vercel
  2. Select the specific project your are working
  3. Go to the settings available on the tab above the project
  4. Select Environment variables on the drop down menu on the right.
  5. Manually define and add your env variables or upload your .env file.

Upvotes: 0

Anthony Reynolds
Anthony Reynolds

Reputation: 1

In order to get nodemailer to work in production on Vercel (and other deployment platforms) You must wrap your .sendMail({}) function in async await. Like so:

const yourFunctionForSendingMail = async () => {
  async function main () {
    await transporter.sendMail({

    // Rest of your email code here

    })
  }

  await main().catch(console.error)

}

Then make sure you await the imported code called from

await yourFunctionForSendingMail()

Upvotes: 0

Minh Pham
Minh Pham

Reputation: 11

const transporter = nodemailer.createTransport({
  host: smtp.gmail.com,
  port: 587,
  secure: false,
  auth: {
    user: '[email protected]',
    pass: 'yourpassword'
  }
})

export const sendEmail = async (to: string, subject: string, text: string) => {
  try {
    const info = await transporter.sendMail({
      from: 'Minh Pham',
      to,
      subject,
      text
    })

    console.log('Email sent: ' + info.response)
  } catch (error) {
    console.error('Error sending email: ', error)
  }
}

export const resetPassword = async (req: Request, res: Response, next: NextFunction) => {
 await sendEmail('to', 'subject', 'text')
}

=> It works fine on vercel

Upvotes: 1

David Perez
David Perez

Reputation: 1

I had the same problem, I solved it only by adding async/await, (make sure to use await transporter) in your case it would be:

const name = async() => { await transporter.sendmail(mailOptions).then(..).catch(..)}

Upvotes: 0

paraj bhatt
paraj bhatt

Reputation: 1

Just Add App password into email_password of env it will work.

Upvotes: 0

Jay Elemar Termulo
Jay Elemar Termulo

Reputation: 1

i encounter this same problem in nodemailer and nextjs14, and its very frustrating but i think i will not use nodemailer with nextjs14 as it have been on nextjs documentation that they block SMTP connection, here is the link as reference.

https://vercel.com/guides/sending-emails-from-an-application-on-vercel

Upvotes: 0

Tomas Lucena
Tomas Lucena

Reputation: 1536

in case someone bump on this in the future:

for nuxt3 in vercel (is not next but it should apply too)

export default defineEventHandler(async (event) => {
  await mailTransport.sendMail(mailOptions);

  return {}
}

You need to remove the callback from the nodemailer sendEmail function otherwise it does not return a promise... this cause the script in vercel to be terminated earlier hence not email being sent!

If callback argument is not set then the method returns a Promise object. Nodemailer itself does not use Promises internally but it wraps the return into a Promise for convenience.

also applicable to nodejs

Upvotes: 7

Jesus Jimenez
Jesus Jimenez

Reputation: 361

I tried all the async/await responses and didn't work at the beginning. Digging through the real time functions logs of the app, I noticed that there was an Error: Missing credentials for "PLAIN", so all I had to do was add the respective .env variables to vercel environment variables and it worked. Here's the complete code though:

import type { NextApiRequest, NextApiResponse } from 'next'

type Data = any
const nodemailer = require('nodemailer')

const auth = {
  user: process.env.WEB_MAILER,
  pass: process.env.WEB_MAILER_PASSWORD,
}

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  const { name, email, subject, message } = req.body

  const mailData = {
    to: process.env.EMAIL_TO,
    from: process.env.WEB_MAILER,
    name: name,
    subject: subject,
    text: `Email: ${email}.\n\nMessage: ${message}`,
    html: `<div>Email: ${email}.\n\nMessage: ${message}</div>`,
  }

  const transporter = nodemailer.createTransport({
    host: 'smtp.titan.email',
    secure: true,
    port: 465,
    auth: auth,
  })

  const server = await new Promise((resolve, reject) => {
    // verify connection configuration
    transporter.verify(function (error: any, success: any) {
      if (success) {
        resolve(success)
      }
      reject(error)
    })
  })
  if (!server) {
    res.status(500).json({ error: 'Error failed' })
  }

  const success = await new Promise((resolve, reject) => {
    // send mail
    transporter.sendMail(mailData).then((info: any, err: any) => {
      if (info.response.includes('250')) {
        resolve(true)
      }
      reject(err)
    })
  })

  if (!success) {
    res.status(500).json({ error: 'Error sending email' })
  }
  res.status(200).json({ success: success })
}

Upvotes: 0

Ignatius Fidelis
Ignatius Fidelis

Reputation: 19

In my own case, wrapping my email function with async solved it for me.

eg:

const sendMessage = async(message)=>{
  await transporter.sendMail({...options here})
}

Then in my API I called my function using:

await sendMessage('your message')

Upvotes: 1

Bryan Bandela
Bryan Bandela

Reputation: 21

I had a similar issue with Nodemailer but I fixed it by first adding the environment variables in Vercel then commit to the github(It will automatically be uploaded on vercel). So add the variables to vercel first for it to take effect

Upvotes: 2

shuggs
shuggs

Reputation: 541

I ran into this issue and managed to fix it and keep using nodemailer by adding in promises with async/await.

const nodemailer = require("nodemailer");

export default async (req, res) => {

const { firstName, lastName, email, message } = JSON.parse(req.body);

const transporter = nodemailer.createTransport({
    port: 465,
    host: "smtp.gmail.com",
    auth: {
        user: "[email protected]",
        pass: "password",
    },
    secure: true,
});

await new Promise((resolve, reject) => {
    // verify connection configuration
    transporter.verify(function (error, success) {
        if (error) {
            console.log(error);
            reject(error);
        } else {
            console.log("Server is ready to take our messages");
            resolve(success);
        }
    });
});

const mailData = {
    from: {
        name: `${firstName} ${lastName}`,
        address: "[email protected]",
    },
    replyTo: email,
    to: "[email protected]",
    subject: `form message`,
    text: message,
    html: `${message}`,
};

await new Promise((resolve, reject) => {
    // send mail
    transporter.sendMail(mailData, (err, info) => {
        if (err) {
            console.error(err);
            reject(err);
        } else {
            console.log(info);
            resolve(info);
        }
    });
});

res.status(200).json({ status: "OK" });
};

Upvotes: 54

yvog
yvog

Reputation: 81

This problem is really confusing indeed. I've managed to fix this by simply adding async/await. This is because streaming responses (fire-and-forget functions) are not supported by Vercel.

Source: https://vercel.com/docs/platform/limits#streaming-responses

Upvotes: 8

enoch
enoch

Reputation: 3123

I have already encountered the same problem, nodemailer was not working on vercel but on heroku everything worked perfectly. it is specified in the doc that vercel does not block stmp connections but according to what I have experienced, in practice stmp connections are blocked. what you can do is use an alternative to nodemailer. use sendgrid and it works fine

An article on how integrating Sendgrid with Next.js

Upvotes: 2

Related Questions