Search code examples
node.jsnext.jstwiliosmswhatsapp

Sometimes Twilio SMS and Whatsapp logic is not firing in serverless function using Next.js API route on vercel production environment


This is the code for the NextJS API route, it is working on local environment.

export default async function handler(request, response) {
  const accountSid = process.env.TWILIO_ACCOUNT_SID;
  const authToken = process.env.TWILIO_AUTH_TOKEN;
  const client = require('twilio')(accountSid, authToken);
  await connectMongo();
  const yesterday = subDays(new Date(), 1); 
  const pendingReminders = await Reminder.find({
    reminderSent: false,
    eventCanceled: false,
    reminderDate: {
      $gte: yesterday, // Greater than or equal to yesterday
      $lte: new Date(), // Less than or equal to the current time
    },
  })
  const currentDate = new Date();
  const currentMonth = currentDate.getMonth() + 1; // Adding 1 because months are zero-based
  const currentYear = currentDate.getFullYear();

  console.log('pending', pendingReminders)

  // TODO use Promise.all to handle all in paralell
  for (let i = 0; i < pendingReminders.length; i++) {
    const reminder = pendingReminders[i];
    let monthlyCount = await ReminderMonthlyCount.findOne({
      userId: reminder.userId,
      month: currentMonth,
      year: currentYear
    });
    if(!monthlyCount) {
      monthlyCount = await ReminderMonthlyCount.create({
        userId: reminder.userId,
        month: currentMonth,
        year: currentYear
      })
    }

    if(monthlyCount.count < 20) {
      let settings = await Settings.findOne({userId: reminder.userId})
      if(!settings) {
        settings = await Settings.create({userId: reminder.userId})
      }
      console.log(`Sending message to ${reminder.phoneNumber} from ${reminder.userId}. Scheduled for ${reminder.reminderDate}. Now it is ${new Date()}`);
      client.messages
      .create({
         from: `whatsapp:${process.env.TWILIO_WHATSAPP_NUMBER}`,
         // Whatsapp templates must be approved before on https://console.twilio.com/us1/develop/sms/senders/whatsapp-templates
         // TODO Not working right now
         body: replaceValues(settings?.reminderMessage, [settings?.personalOrCompanyName || 'us', format(new Date(reminder.reminderDate), "eeee 'at' h:mma"), `${process.env.NEXT_PUBLIC_WEB_URL}appointment/cancel/${reminder['_id']}`]),
         to: `whatsapp:${reminder.phoneNumber}`
       })
      .then(async message => {
        console.log(`${message.sid} message sent to ${reminder.phoneNumber} from ${reminder.userId}. Scheduled for ${reminder.reminderDate}. Now it is ${new Date()}`);
        monthlyCount.count = monthlyCount.count + 1
        reminder.reminderSent = true

        await monthlyCount.save()
        await reminder.save()
      });
    }
  }
  response.status(200).json({ok: true});
}

The problem is that when I deploy it to Vercel, it has erratic behavior. Sometimes it sends the message and sometimes it doesn't. It does not throw an error or anything. It just returns 200 ok


Solution

  • I think there may be a race condition between the message being sent and the API call ending before the promise resolves.

    To solve this you have to switch from promises to async/await so that you know the promise and code is completed before returning from the function.

    Code refactor suggestion:

    export default async function handler(request, response) {
      const accountSid = process.env.TWILIO_ACCOUNT_SID;
      const authToken = process.env.TWILIO_AUTH_TOKEN;
      const client = require('twilio')(accountSid, authToken);
      await connectMongo();
      const yesterday = subDays(new Date(), 1); 
      const pendingReminders = await Reminder.find({
        reminderSent: false,
        eventCanceled: false,
        reminderDate: {
          $gte: yesterday, // Greater than or equal to yesterday
          $lte: new Date(), // Less than or equal to the current time
        },
      })
      const currentDate = new Date();
      const currentMonth = currentDate.getMonth() + 1; // Adding 1 because months are zero-based
      const currentYear = currentDate.getFullYear();
    
      console.log('pending', pendingReminders)
    
      // TODO use Promise.all to handle all in paralell
      for (let i = 0; i < pendingReminders.length; i++) {
        const reminder = pendingReminders[i];
        let monthlyCount = await ReminderMonthlyCount.findOne({
          userId: reminder.userId,
          month: currentMonth,
          year: currentYear
        });
        if(!monthlyCount) {
          monthlyCount = await ReminderMonthlyCount.create({
            userId: reminder.userId,
            month: currentMonth,
            year: currentYear
          })
        }
    
        if(monthlyCount.count < 20) {
          let settings = await Settings.findOne({userId: reminder.userId})
          if(!settings) {
            settings = await Settings.create({userId: reminder.userId})
          }
          try {
            console.log(`Sending message to ${reminder.phoneNumber} from ${reminder.userId}. Scheduled for ${reminder.reminderDate}. Now it is ${new Date()}`);
            const message = await client.messages.create({
              from: `whatsapp:${process.env.TWILIO_WHATSAPP_NUMBER}`,
              // Whatsapp templates must be approved before on https://console.twilio.com/us1/develop/sms/senders/whatsapp-templates
              body: replaceValues(settings?.reminderMessage, [settings?.personalOrCompanyName || 'us', format(new Date(reminder.reminderDate), "eeee 'at' h:mma"), `${process.env.NEXT_PUBLIC_WEB_URL}app/appointment/cancel/${reminder['_id']}`]),
              to: `whatsapp:${reminder.phoneNumber}`
            })
    
            console.log(`${message.sid} message sent to ${reminder.phoneNumber} from ${reminder.userId}. Scheduled for ${reminder.reminderDate}. Now it is ${new Date()}`);
            monthlyCount.count = monthlyCount.count + 1
            reminder.reminderSent = true
    
            await monthlyCount.save()
            await reminder.save()
    
          } catch(err) {
            console.log('Twilio Client error: ', err?.message || err)
          }
        }
      }
      response.status(200).json({ok: true});
    }