4

I am using firebase cloud functions. I have the following setup configured. While that is working completely fine on my local machine, It's giving me an issue when run on the servers. I have tried gazillion work arounds on the internet but no luck. What is wrong with this?

'use strict'
const functions = require('firebase-functions');
var admin = require('firebase-admin');
const express = require('express');
const nodemailer = require('nodemailer');
const app = express()

var emailRecepient;
var userName;


const smtpTransport = nodemailer.createTransport({
service: "gmail",
host: 'smtp.gmail.com',
port: 587, // tried enabling and disabling these, but no luck
secure: false, // similar as above
auth: {
  user: '<emailid>',
  pass: '<password>'
    },
        tls: {
        rejectUnauthorized: false
    }
});

var mailOptions = {
  from: 'test <hello@example.com>',
  to: emailRecepient,
  subject: 'Welcome to test',
  text: 'welcome ' + userName + ". did you see the new things?"
};

function sendmail() {
smtpTransport.sendMail(mailOptions, function (error, info) {
    if (error) {
        console.log(error);
    }
    else {
        console.log('Email sent: ' + info.response);
    }
});
};

exports.sendEmails = functions.database.ref('/users/{userID}/credentials').onCreate((snap, context) => {

  const userID = context.params.userID;
  const vals = snap.val()

  userName = vals.name;
  emailRecepient = vals.email;

  smtpTransport.sendMail(mailOptions, function (error, info) {
  if (error) {
      console.log("Error sending email ---- ",error);
  }
  else {
      console.log('Email sent: ' + info.response);
  }
  });

  return true;

});

The error I got on all cases is :

        Error sending email 2 ----  { Error: Invalid login: 534-5.7.14 <https://accounts.google.com/signin/continue?sarp=1&scc=1&plt=AKgnsbsi
    534-5.7.14 qRQLfD9YlFZDsPj7b8QQACro9c41PjhSVo0NZ4i5ZHNlyycFi_FyRp8VdZ_dH5ffWWAABQ
    534-5.7.14 8rH2VcXkyZBFu00-YHJUQNOqL-IqxEsZqbFCwCgk4-bo1ZeDaKTdkEPhwMeIM2geChH8av
    534-5.7.14 0suN293poXFBAk3TzqKMMI34zCvrZlDio-E6JVmTrxyQ-Vn9Ji26LaojCvdm9Bq_4anc4U
    534-5.7.14 SpQrTnR57GNvB0vRX1BihDqKuKiXBJ5bfozV1D1euQq18PZK2m> Please log in via
    534-5.7.14 your web browser and then try again.
    534-5.7.14  Learn more at
    534 5.7.14  https://support.google.com/mail/answer/78754 t2sm3669477iob.7 - gsmtp
        at SMTPConnection._formatError (/user_code/node_modules/nodemailer-smtp-transport/node_modules/smtp-connection/lib/smtp-connection.js:528:15)
        at SMTPConnection._actionAUTHComplete (/user_code/node_modules/nodemailer-smtp-transport/node_modules/smtp-connection/lib/smtp-connection.js:1231:30)
        at SMTPConnection.<anonymous> (/user_code/node_modules/nodemailer-smtp-transport/node_modules/smtp-connection/lib/smtp-connection.js:319:22)
        at SMTPConnection._processResponse (/user_code/node_modules/nodemailer-smtp-transport/node_modules/smtp-connection/lib/smtp-connection.js:669:16)
        at SMTPConnection._onData (/user_code/node_modules/nodemailer-smtp-transport/node_modules/smtp-connection/lib/smtp-connection.js:493:10)
        at emitOne (events.js:96:13)
        at TLSSocket.emit (events.js:188:7)
        at readableAddChunk (_stream_readable.js:176:18)
        at TLSSocket.Readable.push (_stream_readable.js:134:10)
        at TLSWrap.onread (net.js:559:20)
      code: 'EAUTH',
      response: '534-5.7.14 <https://accounts.google.com/signin/continue?sarp=1&scc=1&plt=AKgnsbsi\n534-5.7.14 qRQLfD9YlFZDsPj7b8QQACro9c41PjhSVo0NZ4i5ZHNlyycFi_FyRp8VdZ_dH5ffWWAABQ\n534-5.7.14 8rH2VcXkyZBFu00-YHJUQNOqL-IqxEsZqbFCwCgk4-bo1ZeDaKTdkEPhwMeIM2geChH8av\n534-5.7.14 0suN293poXFBAk3TzqKMMI34zCvrZlDio-E6JVmTrxyQ-Vn9Ji26LaojCvdm9Bq_4anc4U\n534-5.7.14 SpQrTnR57GNvB0vRX1BihDqKuKiXBJ5bfozV1D1euQq18PZK2m> Please log in via\n534-5.7.14 your web browser and then try again.\n534-5.7.14  Learn more at\n534 5.7.14  https://support.google.com/mail/answer/78754 t2sm3669477iob.7 - gsmtp',
      responseCode: 534,
      command: 'AUTH PLAIN' }

I have even turned of the allow secure apps in the google settings. But for some reason this doesn't seem to work. Any help is extremely appreciated.

As advised by Renaud, I tried firebase-samples/email-confirmation and I am having following error :

TypeError: snapshot.changed is not a function
at exports.sendEmailConfirmation.functions.database.ref.onWrite (/user_code/index.js:38:17)
at Object.<anonymous> (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:112:27)
at next (native)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:28:71
at __awaiter (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:24:12)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:82:36)
at /var/tmp/worker/worker.js:758:24
at process._tickDomainCallback (internal/process/next_tick.js:135:7)

Cheers

Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
Aakash Dave
  • 866
  • 1
  • 15
  • 30

3 Answers3

2

When you execute an asynchronous operation in a background triggered Cloud Function, you must return a promise, in such a way the Cloud Function waits that this promise resolves in order to terminate.

This is very well explained in the official Firebase video series here: https://firebase.google.com/docs/functions/video-series/. In particular watch the three videos titled "Learn JavaScript Promises" (Parts 2 & 3 especially focus on background triggered Cloud Functions, but it really worth watching Part 1 before).

So you should modify your code as follows:

exports.sendEmails = functions.database.ref('/users/{userID}/credentials').onCreate((snap, context) => {

  const userID = context.params.userID;
  const vals = snap.val()

  userName = vals.name;
  emailRecepient = vals.email;

  return smtpTransport.sendMail(mailOptions);
});

If you want to print to the console the result of the email sending, you can do as follows:

  return smtpTransport.sendMail(mailOptions)
    .then((info) => console.log('Email sent: ' + info.response))
    .catch((error) => console.log("Error sending email ---- ", error));
});

Actually there is an official Cloud Functions sample that does exactly that, see https://github.com/firebase/functions-samples/blob/master/email-confirmation/functions/index.js

Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
  • I have tried everything exactly as you said and even what the article said. I am having the same error. While I totally agree to your point that firebase need promises, I am failing to produce the results. All my other functions are working fine. Also, I tried cloning the sample repo and then following the steps. But I am really unable to achieve the results. – Aakash Dave Dec 01 '18 at 20:54
  • The log excerpt that is in your question does not seem to come from the Cloud Functions log (if I am not mistaking). You will find the CF log in the Firebase console. – Renaud Tarnec Dec 02 '18 at 17:14
  • There is no etror. I was logging this statement just to see how if there was any issue. And it just prints this. Technically the function runs, but shows this error when the transport runs the app – Aakash Dave Dec 02 '18 at 18:01
  • Apparently a problem of logging to your gmail account... Sorry I cannot help you further, apart from what I have said in my answer about the importance of returning a promise in your Cloud Function. Maybe you could try with a "brand new" gmail account. – Renaud Tarnec Dec 02 '18 at 18:07
  • Sure, appreciate your input Renaurd. I will try a fresh account. Just in case, if I have a function log, I’ll update you. – Aakash Dave Dec 02 '18 at 18:11
  • There was another error, When I tried the firebase sample. I even did create another gmail acount and also a completely new firebase project. I have updated the error in my question above – Aakash Dave Dec 03 '18 at 01:34
  • For your new error, see https://firebase.google.com/docs/functions/beta-v1-diff. You most probably have a mismatch between your code and CF version. – Renaud Tarnec Dec 03 '18 at 06:43
  • Well this error is from the firebase-sample code. Trying a complete step by step procedure is giving me this issue. If I run my code, it's doesn't produce any besides one that I mentioned. I am still giving it a try by creating a fresh firebase init and them trying this out. I am really lost on this, to be honest. I am not able to figure out whats going wrong exactly – Aakash Dave Dec 03 '18 at 07:21
  • Sorry, my last comment was not accurate (was commuting and looking at the post from a smart phone...) Actually it seems there is an error in the official sample code. `changed()` is indeed not a method for a `DataSnapshot` see https://firebase.google.com/docs/reference/admin/node/admin.database.DataSnapshot. Just give a try without this check. – Renaud Tarnec Dec 03 '18 at 13:31
  • 1
    It turns out that gmail security rules takes 24-48 hrs to come into effect. I guess, I'll be able to know after that itself. I even heard from firebase team but its been on no use. I check their past raised issues. It's been advised to wait. So yeah. I'll keep you posted. – Aakash Dave Dec 04 '18 at 07:23
  • Hey @renaud, I am having issues here. Can you help me out? -> https://stackoverflow.com/questions/53702633/firebase-doesnt-update-new-information-while-performing-a-search-query – Aakash Dave Dec 11 '18 at 03:45
  • @AakashDave Sorry I cannot help you, I don't know Swift at all. Did you have time to check my answer above? You may accept and upvote it, thanks! – Renaud Tarnec Dec 11 '18 at 08:56
  • Thanks for replying. Actually while your solution is correct I a still not able to get the results. I have raised an issue with the firebase team. Even they are unable to solve this issue. I am sending them a recorded video so this can be escalated to a higher team – Aakash Dave Dec 11 '18 at 08:59
  • 1
    Thanks for the feedback. FYI, using Sendgrid works very well. There is a free tier of 12000 mails a month. – Renaud Tarnec Dec 11 '18 at 09:01
  • Well, wont using sendgrid require a paid firebase plan? – Aakash Dave Dec 11 '18 at 09:03
  • Yes, but there are great chances that you can stay under the free tier limit. – Renaud Tarnec Dec 11 '18 at 09:04
  • Oh I see. I am checking it out then. Thanks for all the help. may come to you if stuck :) – Aakash Dave Dec 11 '18 at 09:06
  • 1
    See the "discussion" (i.e. the comments) on this SO response: https://stackoverflow.com/questions/53481773/firebase-cloud-function-send-mail-without-auth/53481921?r=SearchResults#53481921 – Renaud Tarnec Dec 11 '18 at 09:06
  • 1
    Hey, just a quick update. It's working well right now! Although I am opting for sendgrid just to save time on templating the mail. Cheers for the help. – Aakash Dave Dec 13 '18 at 23:37
  • @AakashDave What did you do that it is working well right now? Even I am having the same problem. – Ashish Yadav Apr 01 '19 at 04:19
  • 1
    @AshishYadav Sorry for the late reply mate! I had to signout from all my accounts and had sign in from a different browser. Just follow all the protocols again. I dont know why but it started for me in that case. Thats what even Firebase team replied. Even try clearing cache or history – Aakash Dave Apr 20 '19 at 03:02
0

I see it was a long discussion, let me share the code snippets which worked out for me for others with similar issues so it will be easier to figure out.

1) function.ts (it's written in TypeScript)

import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
import * as nodemailer from 'nodemailer';

admin.initializeApp();

// I'm taking all these constants as secrets injected dynamically (important when you make `git push`), but you can simply make it as a plaintext.
declare const MAIL_ACCOUNT: string; // Declare mail account secret.
declare const MAIL_HOST: string; // Declare mail account secret.
declare const MAIL_PASSWORD: string; // Declare mail password secret.
declare const MAIL_PORT: number; // Declare mail password secret.

const mailTransport = nodemailer.createTransport({
  host: MAIL_HOST,
  port: MAIL_PORT, // This must be a number, important! Don't make this as a string!!
  auth: {
    user: MAIL_ACCOUNT,
    pass: MAIL_PASSWORD
  }
});

exports.sendMail = functions.https.onRequest(() => {
  const mailOptions = {
    from: 'ME <SENDER@gmail.com>',
    to: 'RECEIVER@gmail.com',
    subject: `Information Request from ME`,
    html: '<h1>Test</h1>'
  };
  mailTransport
    .sendMail(mailOptions)
    .then(() => {
      return console.log('Mail sent'); // This log will be shown in Firebase Firebase Cloud Functions logs.
    })
    .catch(error => {
      return console.log('Error: ', error); // This error will be shown in Firebase Cloud Functions logs.
    });
});

This said, you should receive an e-mail from SENDER@gmail.com to RECEIVER@gmail.com, of course modify it for your own needs.

Note: I got the same issue with sending mails correctly on localhost, but on deployment it did not. Looks like the problem in my case was that I did not use port and host in createTransport, instead I had:

const mailTransport = nodemailer.createTransport({
  service: 'gmail',
  auth: {
    user: MAIL_ACCOUNT,
    pass: MAIL_PASSWORD
  }
});

On top of this do not forget about enabling Less secure app access to ON. Also https://accounts.google.com/DisplayUnlockCaptcha might be helpful.

Daniel Danielecki
  • 8,508
  • 6
  • 68
  • 94
0

After checking that all the things listed above were done within my code, I solved the problem login in Google´s account (with the account I'm using with my proyect). I had to recognize the activity form another origin (google detected Firebase was trying to access to that account), and that was it. After that I tryed to send another email from firebase cloud and it was working fine