Search code examples
node.jsfirebasegoogle-cloud-functionstimeoutnodemailer

Firebase function using SMTP is working in emulator but time outs when deployed


Currently i'm writing a cloud function in Firebase to send emails with nodemailer, which includes an qr-code.

If I emulate the project in the Firebase emulator everything works fine and the mails are send correctly.

However, when I upload the code to Firebase the function is always timing out after 60s. I already tried to set the timeout higher, but its still happening.

My code is:

exports.emailSender = functions.https.onRequest(async (req, res) => {
  var { name, code, hash, dest } = req.body;
  let transporter = nodemailer.createTransport({
    host: "smtp.test.com",
    port: 25,
    auth: {
      user: "username", 
      pass: "password",
    },
  });

  try {
    let qr = await create_qrcode(code+":"+hash,250,50);
    let qrBig = await create_qrcode(code+":"+hash,500,100);

    const mailOptions = {
      from: "[email protected]",
      to: dest,
      subject: "Email Sent via Firebase",
      html: `<img src="${qr}" />`,
      attachments: [
        {
          filename: "QR-Code",
          path: qrBig,
        },
      ],
    };

    await transporter.sendMail(mailOptions, (err, info) => {
      if (err) {
        console.log(err);
        res.send({ error: err.message });
      } else {
        console.log(info);
        res.send("Erfolgreich");
      }
    });
  } catch (error) {
    res.send({ error: error.message });
  }
});

The function to create the qrcode is:

async function create_qrcode(dataForQRcode, width, cwidth {
  // grab data you want on qrcode here
  const cvs = createCanvas(1, 1);
  const url = await QRCode.toCanvas(cvs, dataForQRcode, {
    errorCorrectionLevel: "H", // LMQH
    margin: 1,
    color: {
      dark: "#000000", // black pixels
      light: "#ffffff", // white background
    },
  });
  const canvas = createCanvas(width, width);
  const ctx = canvas.getContext("2d");
  const img = await loadImage("./icon.png");
  ctx.drawImage(url, 0, 0, width, width);
  const center = (width - cwidth) / 2;
  ctx.drawImage(img, center, center, cwidth, cwidth);
  return canvas.toDataURL("image/png");
};

It seems like the function is freezing at the sendMail() so no res.send() is fired and after 60s the timeout is reached.

Any idea why this is working on the emulator and not on the real cloud functions?


Solution

  • As a way to counteract abuse, GCP entirely blocks outbound port 25 for Compute Engine, and it is almost certain that the same thing is happening here for Cloud Functions.

    You'll have to try another approach that doesn't use outbound port 25. For example, you can use the SMTP submission port 587 (instead of port 25) on your mail server to submit outbound emails. This will work because port 587 (and 465, for smtps) are not blocked by GCP.