javascriptfirebasegoogle-cloud-platformgoogle-cloud-functionssendgrid-api-v3

SendGrid with Firebase Cloud Functions error: "socket hang up"


I have a Cloud Function triggered by a pub/sub event. I use sendgrid nodejs api. The main idea is sending my clients a weekly stats email. sendEmail() function run for each client (80 times). But when I check function logs I see that 25-30 of client emails are sent with success but the remaining it gives that error: "socket hang up"

I shortened the whole code to show the main part related sending email. Here is the last part.

    // I shortened the whole function as it is a very long function.
    // The main and the last part is as below
    // I have nearly 80 clients and sendEmail function run for each client.

    function calcData(i, data) {
        return admin.database().ref('clientUrlClicks/' + data.key)
            .orderByChild('date')
            .startAt(dateStartEpox)
            .endAt(dateEndEpox)
            .once('value', urlClickSnap => {
                clients[i].clickTotalWeek = urlClickSnap.numChildren();
                clients[i].listTotalWeek = 0;
                admin.database().ref('clientImpressions/' + data.key)
                    .orderByKey()
                    .startAt(dateStart)
                    .endAt(dateEnd)
                    .once('value', snap => {
                        snap.forEach(function(impressionSnap) {
                            clients[i].listTotalWeek += impressionSnap.val();
                        })
                    }).then(resp => {
                        return sendEmail(i, clients[i]);
                    }).catch(err => {
                        console.log(err);
                    });
            }).catch(err => {
                clients[i].clickTotalWeek = 0;
                console.log(err);
            });
    }

   function sendEmail(i, data) {
        var options = {
            method: 'POST',
            url: 'https://api.sendgrid.com/v3/mail/send',
            headers:
            {
                'content-type': 'application/json',
                authorization: 'Bearer ' + sgApiKey
            },
            body:
            {
                personalizations:
                    [{
                        to: [{ email: data.email, name: data.name }],
                        dynamic_template_data:
                        {
                            dateStart: xxx,
                            dateEnd: xxx,
                        }
                    }],
                from: { email: 'info@xxx.com', name: 'xxx' },
                reply_to: { email: 'info@xxx.com', name: 'xxx' },
                template_id: 'd-f44eeexxxxxxxxxxxxx'
            },
            json: true
        };

        request(options, function (error, response, body) {
            if (error) {
                console.log("err: " + error);
                return;
            }
            return;
        });
    }

Edit:

In addition to answers below related to "chaining the promises correctly", I also added all emails and personalizations to "personalizations" array as an object on "sendEmail" function. So, instead making a request for each email I make one request. No problem now.


Solution

  • You are not chaining the promises correctly and therefore not returning a final promise at the end of the chaining, which is mandatory for a Cloud Function.

    The following set of modifications is a first attempt to solve this problem.

    Also, it is not crystal clear how do you call Sendgrid and return the Promise returned by the Sendgrid call. I would suggest that you use the send() method, which returns a Promise, as explained in the doc of the Sendgrid v3 Web API for Node.js, see https://github.com/sendgrid/sendgrid-nodejs/tree/master/packages/mail.

    function calcData(i, data) {
            //Declare clients aray here
            return admin.database().ref('clientUrlClicks/' + data.key)
                .orderByChild('date')
                .startAt(dateStartEpox)
                .endAt(dateEndEpox)
                .once('value')
                .then(urlClickSnap => {
                    clients[i].clickTotalWeek = urlClickSnap.numChildren();
                    clients[i].listTotalWeek = 0;
                    return admin.database().ref('clientImpressions/' + data.key)  //Here you didn't return the promise
                        .orderByKey()
                        .startAt(dateStart)
                        .endAt(dateEnd)
                        .once('value');
                 .then(snap => {
                         snap.forEach(function(impressionSnap) {
                             clients[i].listTotalWeek += impressionSnap.val();
                        })
                        return sendEmail(i, clients[i]);
                 }).catch(err => {
                    clients[i].clickTotalWeek = 0;
                    console.log(err);
                    return null;
                });
        }