2

I am working on a Serverless AWS service that uses Twilio's programmable SMS to deliver text messages.

My setup is consistently delivering messages successfully when I run the stack locally (e.g. sls offline start), but in the deployed environment I seem to be unable to even call the method on the Twilio client.

Here's how the message delivery is set up:

const twilio = require('twilio');

const twilioClient = twilio(
  process.env.TWILIO_SID,
  process.env.TWILIO_TOKEN,
  {
    lazyLoading: true,
  }
);

export function sendMessage(user, message) {
  twilioClient.messages.create({
    from: process.env.TWILIO_NUMBER,
    to: user.phone,
    body: message,
  }, function(err, message) {
    console.log('error', err);
    console.log('message', message);
  });
}

// And then usage in a Serverless Function Handler

function example(event, context, callback) {
  context.callbackWaitsForEmptyEventLoop = false;

  // user is also determined here
  sendMessage(user, 'This is a message');

  return {
    body: JSON.stringify({}),
    statusCode: 200
  };
}

Locally, running this works and I am able to see the output of the message log, with nothing on the error log. However, when deployed, running this yields nothing -- the method appears to not even get called (and I can verify in the Twilio logs that no API call was made), therefor no error or message logs are made in the callback.

In debugging I've tried the following:

  • I've logged all the environment variables (Twilio SSID, auth token, phone number), as well as the function arguments, and they all appear to be in place. I also checked out the Lambda function itself to make sure the environment variables exist.
  • I've examined my CloudWatch logs; no errors or exceptions are logged. Other than the Twilio method not getting called the Lambda function is executed without issue.
  • I've tried logging things like twilio and twilioClient.messages.create to make sure the client and function definition didn't get wiped out somehow.
  • I thought maybe it had to do with context.callbackWaitsForEmptyEventLoop so I changing it from false to true.

I have come up empty, I can't figure out why this would be working locally, but not when deployed.


Edit: per the Twilio client example, if you omit the callback function the method will return a Promise. I went ahead and tried to await the response of the method:

export function sendMessage(user, message) {
  return twilioClient.messages.create({
    from: process.env.TWILIO_NUMBER!,
    to: user.phone,
    body: message,
  });
}

// Usage...

async function example(event, context, callback) {
  context.callbackWaitsForEmptyEventLoop = false;

  try {
    const message = await sendMessage(user, 'This is a message');
    console.log('message', message)
  } catch (error) {
    console.log('error', error);
  }

  return {
    body: JSON.stringify({}),
    statusCode: 200
  };
}

In this example the Lambda function is successful, but neither the message nor the error are logged.

2
  • I haven't used Lambda but with Twilio Functions, you need to me mindful of asynchronous functions and and the associate callback to indicate the end of the function - specifically - twilio.com/docs/runtime/functions/…. Could this be what is going on in this case as well? Maybe some details here? docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html Commented Apr 26, 2020 at 22:34
  • @Alan I suspect you are on to something, but I unfortunately have not been able to solve it. I've updated my question to note that I've given a Promise-based method a shot, with no luck. Commented Apr 27, 2020 at 0:06

1 Answer 1

5

I tried this and it works. I tried to make my code as similar to use, with a few changes.

const twilio = require('twilio');

const twilioClient = twilio(
  process.env.TWILIO_SID,
  process.env.TWILIO_TOKEN
);

let user = '+14075551212';

function sendMessage(user, message) {
  return twilioClient.messages.create({
    from: process.env.TWILIO_NUMBER,
    to: user,
    body: message,
  });
}

exports.handler = async function(event, context, callback) {
  try {
    const message = await sendMessage(user, 'This is a message');
    console.log('message', message);
    callback(null, {result: 'success'});
  } catch (error) {
    console.log('error', error);
    callback("error");
  }
};
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for this, Alan! Sure enough, like you mentioned, this works as written. I'm going to mark this as the answer. Unfortunately, in my case, I think there's something else going on, and I think it's the VPC I have set up on the Lambda function. It's needed to communicate with an RDS instance, and I think somewhere in there it's blocking outbound requests to Twilio. I neglected to mention this in my original post, apologies. If you have any VPC / Subnet / NAT gateway knowledge, I'm all ears. Otherwise, thanks for this answer!
Hi Jody, I do but not my speciality. It may be best for another post. A diagram of your topology would help others provide a more focused answer. I would normally think IAM permissions is more an issue with Lambda but I have not really worked that much with it, 100% on my growing to-do list though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.