A JSON sticker held by someone

How to Build a Pseudo-backend With Netlify Functions

And it is great to host simple MVPs

My Need

Last year, I worked on an application integrating Twilio API for an on-call application.

We had a scenario where the primary person on-call couldn’t reply to the customer calling.

Therefore, after a certain time, a scheduled task would trigger a call to the backup person through the Call API at Twilio.

To do so, we need to make the call in the following manner:

1
2
3
4
message = self.twilio_client.calls.create(
    from_=self.config.TWILIO_PHONE_NUMBER,
    to=on_call_individual.phone_number,
    url=quote(call_instructions, safe=':/')

However, the API checks if the Url parameter is valid.

Guess what? Locally, the Url was valid, but not accessible from Twilio point of view.

Solution

I developed a quick app using Netlify Functions.

The goal was that the Url above would be [http://domain.com/twiml/instructions/call/{dynamic_value}](http://domain.com/twiml/instructions/call/%7Bdynamic_value%7D) and would reply:

1
2
3
4
5
6
<Response>
  <script/>
  <Say>
    <speak-as interpret-as="telephone"> The caller +41123456789 has called the On-call team for a problem. A SMS was sent to you to confirm you're calling the person back. The message contains the caller's number. Thanks. </speak-as>
  </Say>
</Response>

Step 1: Structure Your Project For Netlify

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
twiml-response-app/
├── functions/
│   └── twiml.js
├── public/
│   └── index.html
├── netlify.toml
└── package.json

Step 2: Initialize And Implement The Project

With npm init -y, you can initialize the project.

Then, install the dependencies:

1
@netlify/functions

Next, create the index.html file to provide instructions when loading the base URL.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>TwiML Response App</title>
  </head>
  <body>
    <h1>TwiML Response App</h1>
    <p>
      Use the /twiml/instructions/call/{dynamic_value} endpoint to get TwiML
      responses.
    </p>
    <form>
      <label for="phoneNumber">
        Enter the number:
        <input type="tel" name="phoneNumber" id="phoneNumber" />
      </label>
    </form>
    <section class="link"></section>
    <script>
      const phoneNumberEl = document.querySelector("[name='phoneNumber']");
      const link = document.querySelector(".link");
      phoneNumberEl.addEventListener("keyup", () => {
        event.preventDefault();
        const anchor = document.createElement("a");
        const phoneNumberEncoded = encodeURIComponent(phoneNumberEl.value);
        const href = `${document.location.protocol}//${document.location.host}/twiml/instructions/call/${phoneNumberEncoded}`;
        anchor.innerText = href;
        anchor.href = href;
        anchor.target = "_blank";
        link.innerHTML = "";
        link.appendChild(anchor);
      });
    </script>
  </body>
</html>

Then, we create the function twiml.js under functions directory.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
exports.handler = async function (event, context) {
  const path = event.path.split("/");
  const encodedValue = path[path.length - 1];
  const dynamicValue = decodeURIComponent(encodedValue);
  console.log(`Received request at ${event.path}`);

  const xmlResponse = `<?xml version="1.0" encoding="UTF-8"?>
    <Response>
    <Say>
        The caller <say-as interpret-as="telephone">${dynamicValue}</say-as> has called the On-call team for a problem. A SMS was sent to you to confirm you're calling the person back. The message contains the caller's number. Thanks.
    </Say>
</Response>`;

  console.log(`Replying with ${xmlResponse}`);
  return {
    statusCode: 200,
    headers: {
      "Content-Type": "text/xml",
    },
    body: xmlResponse,
  };
};

Be careful with XML: in the template string, avoid having any leading newline. Otherwise, you’ll get “error on line 2 at column 10: XML declaration allowed only at the start of the document”.

Finally, let’s configure the netlify.toml file with the following content:

1
2
3
4
5
6
7
8
[build]
  functions = "functions"
  publish = "public"

[[redirects]]
  from = "/twiml/*"
  to = "/.netlify/functions/twiml/:splat"
  status = 200

First, we tell Netlify where the functions to run are. Note that, by default, Netlify looks up in the netlify/functions directory if we don’t provide functions = "functions".

Then publish = "public" tell Netlify where is the root directory to serve the application.

Finally, we define a redirect to tell Netlify, on any request to /twiml/*, to call the function with the wildcard placeholder (:splat) that captures and forwards any additional path segments after /twiml/ to the function.

Step 3: Deploy The Application

This step is so simple.

Just create an account at Netlify with your preferred Git provider and deploy the application from your repository.

The default settings work great.

Step 4: Test The Application

Browse to the URL provided by Netlify and enter a value in the input.

Click the link that appears below to preview the XML generated.

Conclusion

There you have it! Now, you can use this hosted application on your local environment and test Twilio API against a URL available on the Internet.

Follow me

Thanks for reading this article. Make sure to follow me on X, subscribe to my Substack publication and bookmark my blog to read more in the future.

Photo by RealToughCandy.com

Licensed under CC BY-NC-SA 4.0
License GPLv3 | Terms
Built with Hugo
Theme Stack designed by Jimmy