Nodemailer 自定义 SMTP 不适用于 Firebase 功能

Nodemailer Custom SMTP not working on Firebase Functions

我有一个错误。我正在使用 nodemailer 从我的 Firebase 应用程序发送电子邮件。

这是我的代码的样子:

const functions = require('firebase-functions');
const admin = require("firebase-admin");
const nodemailer = require('nodemailer');

admin.initializeApp();

//THESE SETTINGS WORK ON LOCAL AND LIVE. BUT I DONT WANT TO USE THEM
// const transporter = nodemailer.createTransport({
//     service: 'gmail',
//     auth: {
//         user: 'GMAIL HERE',
//         pass:  'GMAIL PW HERE'
//     },
// })

//THESE SETTINGS WORK ON LOCAL, BUT NOT ON LIVE.
const transporter = nodemailer.createTransport({
    host: "smtp.mycompanyname.com",
    port: 25,
    secureConnection: false, // TLS requires secureConnection to be false
    logger: true,
    debug: true,
    secure: false,
    requireTLS: true,
    auth: {
        user: "USERNAME HERE",
        pass: "PASSWORD HERE"
    },
    tls: { rejectUnauthorized: false }
})

exports.sendConfirmationEmail = functions.https.onCall((data, context) => {

    var email_adress        = data.data.email;

    var email = {
        from: 'E-Mail Adress Goes Here',
        to: email_adress,
        subject: 'BlaBlaBla',
        text: 'BlaBlaBla',
        html: 'BlaBlaBla'
    };
    // Function to send e-mail to the user
    transporter.sendMail(email, function(err, info) {
        if (err) {
            console.log(err);
            return { success: false };
        } else {
            return { success: true }
        }
    });

})

现在。如果我使用 GMail 设置。一切正常。它发送电子邮件。但是,我的公司有自己的 SMTP 服务器。 SMTP 适用于 Firebase 身份验证电子邮件。它正在成功发送这些电子邮件。

当我将上述配置粘贴到我的本地环境时,SMTP 服务器也可以正常工作。但是,当我 运行 在 Firebase Cloud 函数中使用它时,出现以下错误:

10:24:43.479 AM
sendConfirmationEmail
[2020-09-25 08:24:43] DEBUG [Cq6p67HnXLA] Closing connection to the server using "destroy"
10:24:43.479 AM
sendConfirmationEmail
[2020-09-25 08:24:43] ERROR Send Error: Connection timeout
10:24:44.673 AM
sendConfirmationEmail
{ Error: Connection timeout
10:24:44.673 AM
sendConfirmationEmail
    at SMTPConnection._formatError (/workspace/node_modules/nodemailer/lib/smtp-connection/index.js:784:19) 
10:24:44.674 AM
sendConfirmationEmail
    at SMTPConnection._onError (/workspace/node_modules/nodemailer/lib/smtp-connection/index.js:770:20) 
10:24:44.674 AM
sendConfirmationEmail
at Timeout._connectionTimeout.setTimeout (/workspace/node_modules/nodemailer/lib/smtp-connection/index.js:235:22)

我试过使用不同的 nodemailer 选项,但到目前为止并没有太大的成功。这也使得它很难在本地工作,但当我部署时却没有。

有什么想法吗?

您应该使用 Promises 来管理您的 Cloud Function 的生命周期,它执行一个 returns 承诺的异步操作(即 sendMail() 方法)。有关详细信息,请参阅此 doc

因此,通过使用 sendMail() 方法返回的 promise,而不是回调,它应该可以工作。

exports.sendConfirmationEmail = functions.https.onCall((data, context) => {

    var email_adress = data.data.email;

    var email = {
        from: 'E-Mail Adress Goes Here',
        to: email_adress,
        subject: 'BlaBlaBla',
        text: 'BlaBlaBla',
        html: 'BlaBlaBla'
    };

    return transporter.sendMail(email).then(() => {  // Note the return here
        return { success: false };
    }).catch(error => {
        console.log(error);
        // !! Here, return an instance of functions.https.HttpsError.
        // See the doc: https://firebase.google.com/docs/functions/callable#handle_errors
    });

});

注意: 如果您 不是 使用 Node.js 10 或 12 但 Node.js 8(我猜测是 不是 情况,因为自 2020 年 2 月 15 日起不再允许部署 Node.js 8 个函数。),请阅读以下内容:

如果您在 Cloud Functions 中使用 Node.js 版本 8,请注意您需要使用“Blaze”定价计划。事实上,免费的“Spark”计划“只允许对 Google-owned 服务的出站网络请求”。请参阅 https://firebase.google.com/pricing/(将鼠标悬停在“云功能”标题后面的问号上)

由于您的 SMTP 服务器不是 Google-owned 服务,您需要切换到“Blaze”计划。

我遇到了完全相同的问题,并且进行了一些挖掘,但解决方案非常简单。

解决方案


不能使用在您的 Cloud Function 中使用端口 25 上的出站连接。


所以我将端口更改为 465 并使用了安全连接,它确实有效。

我(随机)在 Tips & Tricks 文档页面上发现了这一点。

PS:同样的限制适用于 Compute Engine(参见 docs)。

  1. 需要火焰
  2. 端口 25 不工作
  3. Node.js 12 有效
  4. tls: { rejectUnauthorized: false } 是使其正常工作的必要条件

下面的工作 (TypeScript) 代码,用例是联系表。

import * as functions from 'firebase-functions';
import * as nodemailer from 'nodemailer';
import * as Mail from 'nodemailer/lib/mailer';
import { DocumentSnapshot } from 'firebase-functions/lib/providers/firestore';
import { EventContext } from 'firebase-functions';

async function onCreateSendEmail(
  snap: DocumentSnapshot,
  _context: EventContext
) {
  try {
    const contactFormData = snap.data();
    console.log('Submitted contact form: ', contactFormData);
    console.log('context: ', _context);

    // The non-null assertion signs (!) might be not required, if your IDE/TypeScript settings aren't strict.
    const mailTransport: Mail = nodemailer.createTransport({
      host: 'mail.yourdomain.com', // Or however your SMTP provides defines it.
      port: 587, // Most probably that's your port number.
      auth: {
        user: 'yourmail@domain.com', // It could happen that your SMTP provides in user authentication requires full name of mail account. For example 'yourmail' would be not correct, 'yourmail@domain.com' would.
        pass: 'YOUR_MAIL_PASSWORD',
      },
      tls: {
        rejectUnauthorized: false, //! ESSENTIAL! Fixes ERROR "Hostname/IP doesn't match certificate's altnames".
      },
    });

    const mailOptions = {
      from: `${contactFormData!.formControlName} <${
        contactFormData!.formControlEmail
      }>`,
      to: 'yourmail@domain.com',
      subject: `Contact Form`,
      html: `
        <p>Message from a contact form has been send.</p>
        <h3>Message content:</h3>
        <ul>
          <li>Name: ${contactFormData!.formControlName}</li>
          <li>E-mail: ${contactFormData!.formControlEmail}</li>
          ...
        </ul>
      `,
    };

    await mailTransport.sendMail(mailOptions);
  } catch (err) {
    console.error(err);
  }
}

exports.contactFormFunction = functions.firestore
  .document('mails/{formControlEmail}'
  )
  .onCreate(onCreateSendEmail);