8

I think I'm missing something very simple here. I have a simple, one page node.js app that uses nodemailer to email any form data to my inbox.

My index.js file:

var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var nodemailer = require('nodemailer');

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

var transporter = nodemailer.createTransport({
    service: 'gmail',
    auth: {
        user: 'myemail@gmail.com',
        pass: 'mypassword'
    }
});

app.use(express.static('public')); //public folder with my html files
app.get('', function (req, res) {
    res.sendFile(__dirname + "/");
})

app.post('/', function (req, res) {
    response = {
        name: req.body.name,
        email: req.body.email,
        message: req.body.message
    };
    var mailClient = {
        from: 'myemail@gmail.com',
        to: 'myemail@gmail.com',
        subject: `Message from ${response.name}`,
        text: 'MyMessage'
    };
    transporter.sendMail(mailClient, function (error, info) {
        if (error) {
            console.log(error); //not happening
        } else {
            res.redirect("/success.html"); //also not happening
        }
    });
})

var server = app.listen(80, function () {
    var host = server.address().address
    var port = server.address().port
    console.log("App listening at http://%s:%s", host, port)
})

When I run this on my local machine using npm start in the root directory, the app runs perfectly fine on localhost. Nodemailer works properly; when I submit, my form data gets emailed to myself and I get redirected to my success page.

However, when I deploy this to Firebase, it seems the nodemailer part doesn't work. The page loads in with my static files, but when I try to submit anything via the form, the page simply refreshes (like when you have a submit button with bare html), instead of redirecting me to my success page and emailing data.

Is there something I need to change in my code to make it work with firebase?

Edit - no logs: enter image description here

Paul
  • 26,170
  • 12
  • 85
  • 119
user7548189
  • 996
  • 6
  • 15
  • 30
  • What do you find in the logs in the console? What payment plan is your project on? – Doug Stevenson Dec 09 '17 at 02:34
  • If you're talking about my website's console, I'm getting no errors. I'm on the free payment plan (spark), – user7548189 Dec 09 '17 at 04:00
  • 1
    What's your nodemailer configuration like? And I'm talking about your Firebase project Functions logs as seen in the Firebase console. – Doug Stevenson Dec 09 '17 at 04:05
  • Since the `console.log(error)` is run in Cloud Functions on the server, the logging will show up in the [Cloud Functions logging panel on the Firebase console](https://console.firebase.google.com/u/0/project/_/functions/logs?search=&severity=DEBUG). – Frank van Puffelen Dec 09 '17 at 04:10
  • I'm getting no logs in the firebase console. I have the latest nodemailer specified in my package.json. – user7548189 Dec 09 '17 at 04:29
  • How do you set up and configure `transporter`? It's hard to help without seeing the [minimal complete code that reproduces the problem](http://stackoverflow.com/help/mcve). But my first guess is that you're trying to reach a mail server that is not on a Google domain. Connecting to external services is only allowed on paid plans. See a.o. https://stackoverflow.com/questions/44259405/sending-mailgun-emails-from-cloud-functions-for-firebase-in-an-angular-2-app – Frank van Puffelen Dec 11 '17 at 04:33
  • I put in my index.js file (minimal on its own to be honest). I'm using gmail for the nodemailer - is there possibly another external service that I'm trying to connect to? – user7548189 Dec 11 '17 at 18:43
  • If you're getting no logs at all, it means your function is not getting invoked. Try running the script with the local Cloud Functions runner `firebase serve --only functions` and see if you can trigger it like that. – Frank van Puffelen Dec 15 '17 at 03:47
  • When I run `firebase serve --only functions`, I get `! functions: Cannot start emulator. Error: Cannot find module 'C:\Users\me\AppData\Roaming\npm\node_modules\firebase-tools\node_modules\grpc\src\node\extension_binary\node-v48-win32-x64-unknown\grpc_node.node'` – user7548189 Dec 16 '17 at 23:26

2 Answers2

3

Google requires a paid account in order to make use of "Outbound Networking". The Free Tier does not allow you to make outbound calls. This would include sending mail to a remote mail server (like sending an email to a Yahoo, Gmail, or Outlook account).

See their pricing page for more info.

Look for "Outbound Networking".

If you'd like to leverage Gmail's API, you should still be able to use nodemailer with firebase functions and achieve what you're looking for and remain on the Free Tier. A fully working example is already available in the firebase-samples repository! I would like to highlight what the linked tutorial mentions, which is that Gmail does have an email sending quota that you should be aware of.

Swivel
  • 3,020
  • 26
  • 36
  • I noticed that the free tier does allow you to make outbound calls on Google services (I guess not sending to another inbox based on your response). Is there a workaround to this without having to upgrade to a paying tier? – user7548189 Dec 11 '17 at 20:28
  • @user7548189 Just updated the answer! `firebase-samples` has an example of how to utilize gmail to send mail via `firebase-functions` and `nodemailer`. As noted, there's a sending quota, but it should work since the Gmail API shouldn't be considered an external service! Hope that helps! – Swivel Dec 12 '17 at 03:31
  • I tested your updated answer, and also tried upgrading to a paid plan (pay as you go). Unfortunately, both did not make a difference. Do you know of anything else that could be causing the problem? – user7548189 Dec 12 '17 at 23:33
1

I tried to figure out problem in your code but didn't found any, I also have functionality to send email with verification code for authenticate/verify email id. For that I create one gmail id and gave that id/password for sending mail. Mails are sent from that gmail id with node.js when ever user register is email id we send email with veirfication code. My code is as under:

'use strict';

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const express = require('express');
var bodyParser = require('body-parser');
var users = require('./users/route');

const app = express();
const nodemailer = require('nodemailer');
// Configure the email transport using the default SMTP transport and a GMail account.
// For other types of transports such as Sendgrid see https://nodemailer.com/transports/
// TODO: Configure the `gmail.email` and `gmail.password` Google Cloud environment variables.
const gmailEmail = 'myapp@gmail.com';
const gmailPassword = 'password';
const gcm = require('node-gcm');
const mailTransport = nodemailer.createTransport(
        `smtps://${gmailEmail}:${gmailPassword}@smtp.gmail.com`);

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({extended: false}))

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: "https://myapp.firebaseio.com"
});
//admin.initializeApp(functions.config().firebase);

// Express middleware that validates Firebase ID Tokens passed in the Authorization HTTP header.
// The Firebase ID token needs to be passed as a Bearer token in the Authorization HTTP header like this:
// `Authorization: Bearer <Firebase ID Token>`.
// when decoded successfully, the ID Token content will be added as `req.user`.
const authenticate = (req, res, next) => {
    if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer')) {
        res.status(403).send('Unauthorized');
        return;
    }
    const idToken = req.headers.authorization.split('Bearer ')[1];
    admin.auth().verifyIdToken(idToken).then(decodedIdToken => {
        req.user = decodedIdToken;
        next();
    }).catch(error => {
        res.status(403).send('Unauthorized');
    });
};

app.get("/token", (req, res) => {

    res.status(200).send(admin.auth().applicationDefault());
    admin.auth().createCustomToken(req.query.uid)
            .then(function (customToken) {
                res.status(200).send(customToken);
            })
            .catch(function (error) {
                console.log("Error creating custom token:", error);
            });

});


// GET /api/verifyEmail?code="1234"
app.get('/verifyEmail', (req, res) => {

    // Get the one-time code from the query parameter.
    var verificationCode = req.query.code;
    var displayName = req.query.displayName;
    var email = req.query.email; //If GET request
    const mailOptions = {
        from: `Linkuni <noreply@firebase.com>`,
        to: email
    };

    // The user subscribed to the newsletter.
    mailOptions.subject = `Welcome to Linkuni`;
    mailOptions.text = `Hi ${displayName || ''}\n\n Welcome to Linkuni. We hope you will enjoy our service. Please enter this code:${verificationCode} into the app.\n\nThank you,\nLinkuni Team`;

    return mailTransport.sendMail(mailOptions).then(() => {
        console.log('Verification email sent to:', email);
    });
});

Hope this helps!! :)

sschunara
  • 2,285
  • 22
  • 31