Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing sending email with Firebase cloud functions

For our web app we our currently using Firebase's free plan, and need to send emails on various events/triggers. The problem is, I thought Mailgun would work perfectly for this task along with their cloud functions but it looks like it's possible to use their API only with Firebase's paid plan which we don't intend to use right now when our app is still under development. So, we're kind of stuck without the knowledge on how to implement email functionality with Firebase with minimum amount of money (we can always pay or pay more when we are a little clear on whether our app will make any profit at all)... Can anyone help me.with any way of doing it in an (almost) free way for now - which we can upgrade any time later on?

Thanks! Piyush Soni

like image 232
Piyush Soni Avatar asked Jun 29 '17 19:06

Piyush Soni


Video Answer


2 Answers

I am using firebase hosting and cloud functions in my personal portfolio site. I created a cloud function (http event) to send emails from the contact form section, I have not enabled the billing option but the free quote (cloud function executions) is still enough for my current needs. I'm sending emails with the following way:

Cloud function:

const functions = require('firebase-functions');
const nodemailer = require('nodemailer');
const rp = require('request-promise');

//google account credentials used to send email
const mailTransport = nodemailer.createTransport(
    `smtps://[email protected]:[email protected]`);

exports.sendEmailCF = functions.https.onRequest((req, res) => {

  //recaptcha validation
  rp({
        uri: 'https://recaptcha.google.com/recaptcha/api/siteverify',
        method: 'POST',
        formData: {
            secret: 'your_secret_key',
            response: req.body['g-recaptcha-response']
        },
        json: true
    }).then(result => {
        if (result.success) {
            sendEmail('[email protected]', req.body).then(()=> { 
              res.status(200).send(true);
            });
        }
        else {
            res.status(500).send("Recaptcha failed.")
        }
    }).catch(reason => {
        res.status(500).send("Recaptcha req failed.")
    })

});

// Send email function
function sendEmail(email, body) {
  const mailOptions = {
    from: `<[email protected]>`,
    to: email
  };
  // hmtl message constructions
  mailOptions.subject = 'contact form message';
  mailOptions.html = `<p><b>Name: </b>${body.rsName}</p>
                      <p><b>Email: </b>${body.rsEmail}</p>
                      <p><b>Subject: </b>${body.rsSubject}</p>
                      <p><b>Message: </b>${body.rsMessage}</p>`;
  return mailTransport.sendMail(mailOptions);
}

Update including contact form and scripts:

<form class="rsForm" action="/sendEmailCF" method="post">
    <div class="input-field">
        <label>Name</label>
        <input type="text" name="rsName" value="">
        <span class="line"></span>
    </div>

    <div class="input-field">
        <label>Email</label>
        <input type="email" name="rsEmail" value="">
        <span class="line"></span>
    </div>

    <div class="input-field">
        <label>Subject</label>
        <input type="text" name="rsSubject" value="">
        <span class="line"></span>
    </div>

    <div class="input-field">
        <label>Message</label>
        <textarea rows="4" name="rsMessage"></textarea>
        <span class="line"></span>
    </div>

    <input type="hidden" name="rsLang" value="en" />

    <span class="btn-outer btn-primary-outer ripple">
        <input class="formSubmitBtn btn btn-lg btn-primary" type="submit" data-recaptcha="global" value="Send">
    </span>
    <div id="recaptcha-global"></div>
</form>

Script to handle form submit:

$('.formSubmitBtn').on('click', function (e) {
    glForm = $(this).closest('.rsForm');
    var recaptchaId = 'recaptcha-' + $(this).data('recaptcha');
    var rsFormErrors = false;
    glFormAction = glForm.attr('action');
    var rsFormFields = glForm.find('.input-field');
    var rsFormName = glForm.find("[name='rsName']");
    var rsFormEmail = glForm.find("[name='rsEmail']");
    var rsFormMessage = glForm.find("[name='rsMessage']");

    // Button ripple effect
    ripple($(this).parent(), e.pageX, e.pageY);

    // Reset form errors
    rsFormFields.removeClass('error');
    rsFormErrors = false;

    // Validate form fields
    if(!rsFormName.val()) {
        rsFormErrors = true;
        rsFormName.parent().addClass('error');
    }

    if(!rsFormEmail.val() || !isValidEmail(rsFormEmail.val())) {
        rsFormErrors = true;
        rsFormEmail.parent().addClass('error');
    }

    if(!rsFormMessage.val()) {
        rsFormErrors = true;
        rsFormMessage.parent().addClass('error');
    }

    if(rsFormErrors) {
        // if has errors - do nothing
        return false;
    } else {

        if(rca[recaptchaId] === undefined){
            rca[recaptchaId] = grecaptcha.render(recaptchaId, {
                'sitekey' : 'sitekey',
                'callback' : onExecutedCaptcha,
                'size' : 'invisible',
                'badge':'inline'
            });

        } else {
            grecaptcha.reset(rca[recaptchaId]);
        }

        grecaptcha.execute(rca[recaptchaId]);
        return false;
    }
});

Script to handle recaptcha response and form post:

function onExecutedCaptcha(token) {

  var sendingMsg = null, textMsg = null, textErr = null;
  var lang = glForm.find("[name='rsLang']").val();

  if(lang == 'es') {
      sendingMsg = 'Enviando mensaje...';
      textMsg = 'Tu mensaje se ha enviado con \u00e9xito!';
      textErr = 'Algo ha salido mal. Intenta mas tarde';
  } else {
      textMsg = 'Your email was sent successfully!';
      textErr = 'Oops! Something went wrong. Please try again.';
      sendingMsg = 'Sending email...';
  }

  swal({
    text: sendingMsg,
    button: false,
    closeOnClickOutside: false,
    closeOnEsc: false,
  });

    $.post( glFormAction,
        glForm.serialize(),
        function (response) {
            grecaptcha.reset();
            var data = jQuery.parseJSON( response );
            swal.close();

            if(data){
                swal({
                  text: textMsg,
                  icon: "success",
                });
                 glForm.find("input[type=text], input[type=email], textarea").val("");
            } else {
                swal({
                  text: textErr,
                  icon: "error",
                });
            }
        }
    );
}
like image 186
Carlos Casallas Avatar answered Oct 21 '22 07:10

Carlos Casallas


If you upgrade to the Blaze plan, you will only be charged for what you use and for Cloud Functions specifically you will still get the free tier number of executions.

I don't know the specifics of your application, but while under development for most apps you are unlikely to incur more than a few dollars a month in charges (and potentially only pennies).

like image 4
Michael Bleigh Avatar answered Oct 21 '22 08:10

Michael Bleigh