API call fails - inconsistent error

Works in dev, not in prod.

With netlify dev, I can send API calls from the frontend to Sendinblue (package name: sib-api-v3-sdk) all day, no problem.

No luck from production deploy. In the function log:

Error: Client network socket disconnected before secure TLS connection was established
and
Error: Timeout of 60000ms exceeded

What’s going on here?

Impacted page: Data Strategy Professionals
Netlify site name: https://build--hungry-noyce-fb652c.netlify.app/

Sorry there’s no public repo for this atm. (Newbie mistake - I deployed my API keys to GitHub at some point :woman_facepalming: I did make a sample repo with a clean history, but it’s not up-to-date).

fwiw, here’s my function. I’m doing something wrong with my error handling because the response I get back for these failing calls is a 200 even though none of these API calls are working.

(i.e. putting through an email that already exists will still print “Adding new email.” Putting in an email that does not exist prints the same. No changes are made within Sendinblue.)

// functions/sendinblue.js
require("dotenv").config();
const fs = require("fs").promises;
const SibApiV3Sdk = require("sib-api-v3-sdk");

var defaultClient = SibApiV3Sdk.ApiClient.instance;
var apiKey = defaultClient.authentications["api-key"];
apiKey.apiKey = process.env.SENDINBLUE;
var apiInstance = new SibApiV3Sdk.ContactsApi();

const headers = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "Content-Type",
};

/* FIX error handling */

export async function handler(event, context) {
  let contactObj = { statusCode: 200, headers, response: "something happened" };
  try {
    const data = JSON.parse(event.body);
    const attributes = {
      FIRSTNAME: data.firstName,
      LASTNAME: data.lastName,
    };

    let payments = await fs.readFile("./src/data/payments.json");
    payments = JSON.parse(payments);
    const product = payments.find((payment) => payment.name === data.product);
    const listId = parseInt(product.list);

    const contacts = [];

    /* Get all contacts */
    const existingContact = await apiInstance
      .getContacts()
      .then(function (output) {
        const stringified = JSON.stringify(output);
        const parsed = JSON.parse(stringified);
        parsed.contacts.forEach((contact) => {
          contacts.push(contact.email);
        });
        console.log("ping");

        return contacts.includes(data.email);
      });

    /* Check if contact is in list */
    if (existingContact) {
      console.log("Existing contact: ", data.email);

      var contactEmails = new SibApiV3Sdk.AddContactToList();
      contactEmails.emails = [data.email];
      apiInstance.addContactToList(listId, contactEmails).then(
        function (output) {
          const message = "API called successfully. Returned data: " + output;
          console.log(message);
          contactObj.response = message;
          return contactObj;
        },
        function (error) {
          console.error(error);
          contactObj.statusCode = error.status;
          contactObj.response = error.error;
          return contactObj;
        }
      );
    } else {
      console.log("Add new contact: ", data.email);
      var createContact = new SibApiV3Sdk.CreateContact();
      createContact.email = data.email;
      createContact.listIds = [listId];
      createContact.attributes = attributes;

      apiInstance.createContact(createContact).then(
        function (output) {
          output = JSON.stringify(output);
          const message = "API called successfully. Returned data: " + output;
          console.log(message);
          contactObj.response = message;
          return contactObj;
        },
        function (error) {
          console.log(error);
          contactObj.statusCode = error.status;
          contactObj.response = error.error;
          return contactObj;
        }
      );
    }
    return contactObj;
  } catch (error) {
    console.log(error);
    contactObj.statusCode = 400;
    contactObj.response = error;
    return contactObj;
  } finally {
    console.log("in finally block");
  }
}

Except for the error handling (seeing “This request has no response data available”), this works fine in dev.

Hey @NicoleJaneway,

Your current thread wasn’t too far in line from your previous one, so here we go.

About SendinBlue, I’ve used it in the past with success, however I didn’t use SendInBlue package. I used emailjs which does the same thing - send emails.

Here’s how I had used it in the past:

Would it be possible for you to try that?

If not, I think we’d need a repo so that we can test this ourselves. Yes, we can copy-paste the code, but having a pre-made setup to test the exact problem is much easier than trying to create the problem.

Thanks for your response, @hrishikesh. I need to add a contact to a Sendinblue list, so I don’t think the approach you outlined is going to work.

I updated the sample repo to reflect the latest: GitHub - NicoleJaneway/netlify-build

Hey @NicoleJaneway,

Your repo was slightly off than testing conditions :slight_smile:, but I was able to get around it. About your problem, I didn’t try to use the library as that would need me to read their documentation and debug their issues. So instead, I went with Axios and got it working:

import Axios from 'axios'
import {promises as fs} from 'fs'
export async function handler(event) {
  const {email, firstName, lastName, product} = JSON.parse(event.body)
  const axiosInstance = Axios.create({
    baseURL: 'https://api.sendinblue.com/v3',
    headers: {
      'api-key': process.env.SENDINBLUE
    }
  })
  return fs.readFile('./src/data/payments.json').then(payments => {
    const requiredProduct = JSON.parse(payments).find(payment => {
      return payment.name === product
    })
    const listId = requiredProduct.list
    const contactList = []
    return axiosInstance({
      url: '/contacts'
    }).then(({data: {contacts}}) => {
      contacts.forEach(contact => {
        contactList.push(contact.email)
      })
      if (contactList.includes(email)) {
        return axiosInstance({
          data: {
            emails: [email]
          },
          method: 'post',
          url: `/contacts/lists/${listId}/contacts/add`
        }).then(() => {
          return {
            body: JSON.stringify({message: 'Success'}),
            statusCode: 200
          }
        })
      } else {
        return axiosInstance({
          data: {
            attributes: {
              FIRSTNAME: firstName,
              LASTNAME: lastName
            },
            email,
            listIds: [listId]
          },
          method: 'post',
          url: '/contacts'
        }).then(() => {
          return {
            body: JSON.stringify({message: 'Success'}),
            statusCode: 200
          }
        })
      }
    }).catch(({response: {data: {message}, status}}) => {
      return {
        body: JSON.stringify(message),
        statusCode: status
      }
    })
  })
}

I’d recommend using Axios (or Node Fetch) for the following reasons:

  1. You’re relying on one-less dependency that can have breaking changes in the future. Yes, we’re swapping the official library with Axios, but I feel Axios is more tried-and-tested than adding a more complex library.
  2. Since we’ll always be using v3 API in this case, it’s more resilient to breaking changes in the future.
  3. We can control the HTTP requests being used which can be easier to debug in my opinion.

I followed this guide for API endpoint reference:

But I believe, you can employ a similar structure using the library if you wish to.

On a side note (but not required), I’d probably recommend storing the environment variable in Netlify UI over using dotenv - but that’s my personal preference.

I’m curious why you think the code I’m using works in dev but not in prod? I would prefer to use the Sendinblue library if I can. This seems like a config issue possibly?

Your repo was slightly off than testing conditions :slight_smile:

^ What do you mean by this?

The repo didn’t have public/index.html file that React needs to work. So the dev server was failing to start until I added that.

About the library, I’d try to put together an example with that by the end of my day today.

I’ve put together an example using the library which works in local as well as production:

The code is here:

import {
  AddContactToList,
  ApiClient,
  CreateContact,
  ContactsApi
} from 'sib-api-v3-sdk'
import {promises as Fs} from 'fs'
export async function handler(event) {
  const {
    email,
    firstName,
    lastName,
    product
  } = JSON.parse(event.body)
  ApiClient.instance.authentications['api-key'].apiKey = process.env.SENDINBLUE
  return Fs.readFile('./src/payments.json').then(payments => {
    const contactList = []
    const listId = JSON.parse(payments).find(payment => {
      return payment.name === product
    }).list
    const apiInstance = new ContactsApi()
    return apiInstance.getContacts({}).then(({contacts}) => {
      contacts.forEach(contact => {
        contactList.push(contact.email)
      })
      if (contactList.includes(email)) {
        const contactEmails = new AddContactToList()
        contactEmails.emails = [email]
        return apiInstance.addContactToList(listId, contactEmails).then(() => {
          return {
            body: JSON.stringify({message: 'Success'}),
            statusCode: 200
          }
        })
      } else {
        const newContact = new CreateContact()
        newContact.listIds = [listId]
        newContact.email = email
        newContact.attributes = {
          FIRSTNAME: firstName,
          LASTNAME: lastName
        }
        return apiInstance.createContact(newContact).then(() => {
          return {
            body: JSON.stringify({message: 'Success'}),
            statusCode: 200
          }
        })
      }
    })
  }).catch(error => {
    return {
      body: JSON.stringify(error),
      statusCode: 500
    }
  })
}

Magical :sparkles:

This works - thank you so much!!