# Manage application state with cookies

Due to the ephemeral nature of Functions, application state for purely serverless apps has previously been difficult to manage, or required storing such information in a remote database. Luckily, with access to [cookies](/docs/serverless/functions-assets/functions/headers-and-cookies) with [Runtime Handler](/docs/serverless/functions-assets/handler) version 1.2.1 and later, you can now maintain limited state in your apps through cookies!

Let's create a Function named `state` that leverages per-phone number cookies to store some application state, just like you would with a [more traditional, server-based solution](/docs/messaging/tutorials/how-to-create-sms-conversations/node-js#using-http-cookies-with-webhooks)! Use the following directions to create a Service and your `state` Function:

## Create and host a Function

Before you run any of the examples on this page, create a Function and paste the example code into it. You can create a Function in the Twilio Console or by using the [Serverless Toolkit](/docs/labs/serverless-toolkit).

## Console

If you prefer a UI-driven approach, complete these steps in the Twilio Console:

1. Log in to the [Twilio Console](https://1console.twilio.com) and navigate to **Develop > Functions & Assets**. If you're using the *legacy* Console, open the [**Functions** tab](https://www.twilio.com/console/functions/overview).
2. Functions are contained within **Services**. Click **[Create Service](https://www.twilio.com/console/functions/overview/services)** to create a new **[Service](/docs/serverless/functions-assets/functions/create-service)**.
3. Click **Add +** and select **Add Function** from the dropdown.
4. The Console creates a new [protected](/docs/serverless/functions-assets/visibility) Function that you can rename. The filename becomes the URL path of the Function.
5. Copy one of the example code snippets from this page and paste the code into your newly created Function. You can switch examples by using the dropdown menu in the code rail.
6. Click **Save**.
7. Click **Deploy All** to build and deploy the Function. After deployment, you can access your Function at `https://<service-name>-<random-characters>-<optional-domain-suffix>.twil.io/<function-path>`\
   For example: `test-function-3548.twil.io/hello-world`.

## Serverless Toolkit

The [Serverless Toolkit](/docs/labs/serverless-toolkit) lets you develop locally, deploy projects, and perform other tasks through the [Twilio CLI](/docs/twilio-cli/quickstart).

1. From the CLI, run `twilio serverless:init <YOUR-SERVICE-NAME> --empty` to bootstrap your local environment.
2. Go to your new project directory using `cd <YOUR-SERVICE-NAME>`.
3. In the `/functions` directory, create a JavaScript file whose name reflects the purpose of the Function. For example, `sms-reply.protected.js` for a [protected](/docs/serverless/functions-assets/visibility) Function intended to handle incoming SMS.
4. Add the code example of your choice to the file and save it. Note that a Function can only export a single handler. You need to create separate files to run or deploy multiple examples at once.

After you save the Function code, you can test it locally (and optionally tunnel requests to it using a tool like [ngrok](https://ngrok.com/)), or deploy it.

### Run your Function in local development

Run `twilio serverless:start` from your CLI to start the project locally. The Function(s) in your project are accessible from `http://localhost:3000/sms-reply`

* If you want to test a Function as a [Twilio webhook](/docs/usage/webhooks/getting-started-twilio-webhooks), run: `twilio phone-numbers:update <your Twilio phone number> --sms-url "http://localhost:3000/sms-reply"`\
  This automatically generates an ngrok tunnel from Twilio to your locally running Function, so you can start sending texts to it. You can apply the same process but with the `voice-url` flag instead to test with [Twilio Voice](/docs/voice).
* If your code does *not* connect to Twilio Voice or Messages as a webhook, start your dev server and start an ngrok tunnel in the same command with the `ngrok` flag. For example: `twilio serverless:start --ngrok=""`

### Deploy your Function

To deploy your Function and have access to live url(s), run `twilio serverless:deploy` from your CLI. This deploys your Function(s) to Twilio under a development Environment by default, where they can be accessed from:

`https://<service-name>-<random-characters>-dev.twil.io/<function-path>`

For example: `https://incoming-sms-examples-3421-dev.twil.io/sms-reply`

You can now invoke your Function with HTTP requests, configure it as the [webhook](/docs/usage/webhooks/getting-started-twilio-webhooks) for a Twilio phone number, call it from a Twilio Studio [**Run Function** Widget](/docs/studio/widget-library/run-function), and more.

## Store application state in a cookie

Now that you have your `state` Function, copy over the following code sample, save the Function, and deploy your Service.

```js title="Add counter state to an SMS response"
exports.handler = (context, event, callback) => {
  // Initialize a new Response and some TwiML
  const response = new Twilio.Response();
  const twiml = new Twilio.twiml.MessagingResponse();

  // Cookies are accessed by name from the event.request.cookies object
  // If the user doesn't have a count yet, initialize it to zero. Cookies are
  // always strings, so you'll need to convert the count to a number
  const count = Number(event.request.cookies.count) || 0;

  // Return a dynamic message based on if this is the first message or not
  const message =
    count > 0
      ? `Your current count is ${count}`
      : 'Hello, thanks for the new message!';

  twiml.message(message);

  response
    // Add the stringified TwiML to the response body
    .setBody(twiml.toString())
    // Since we're returning TwiML, the content type must be XML
    .appendHeader('Content-Type', 'text/xml')
    // You can increment the count state for the next message, or any other
    // operation that makes sense for your application's needs. Remember
    // that cookies are always stored as strings
    .setCookie('count', (count + 1).toString());

  return callback(null, response);
};
```

For Twilio SMS, cookies are scoped to the "conversation" between two parties — you can have a unique cookie for each To/From phone number pair. For example, you can store a unique cookie for any messages sent between 415-555-2222 (your number, for example) and 415-555-1111 (the phone number your Function is a webhook for), which will be different from the cookie used between 415-555-3333 and 415-555-1111.

The code here is accepting an incoming Message webhook request, and checking for an incoming cookie named `count`. If that cookie is not present, `count` is initialized to 0, the user message is formatted to indicate the start of a conversation, and the count is incremented then set as a cookie along with the response to the sender. If `count` is already present, its value is included in the message, incremented, and set so that subsequent messages can continue to store the ever-increasing value of `count`.

To test this and observe your stateless Function managing to track state with cookies, you'll need to set your deployed `state` Function as the webhook for your Twilio phone number, as shown next.

> \[!WARNING]
>
> Cookies created in this [specific scenario](https://help.twilio.com/hc/en-us/articles/223136287-How-do-Twilio-cookies-work) (Twilio forwarding SMS messages to your Function or server) are limited to a maximum lifetime of **four hours**, so if a conversation remains idle for more than four hours, it will be automatically cleared. If you require longer-lasting state, you will need to store it in an external source such as a database.
>
> In any other scenario, cookies set by your Function are only subject to the usual [limitations](/docs/serverless/functions-assets/functions/headers-and-cookies/limitations#maximum-header-size).

## Set a Function as a webhook

For your Function to react to incoming SMS or voice calls, it must be set as a [webhook](/docs/usage/webhooks) for your Twilio number. There are a variety of methods to set a Function as a webhook:

![Setting a Function as a Messaging webhook using the webhook dropdown option.](https://docs-resources.prod.twilio.com/bf4eae4ac40fe7d47003a93bca295d5c232e0b372358e73ceff931fee3ccdc4f.png)

## Validate that it works

Now that your Twilio phone number is directing incoming SMS messages to your Function, try sending a short message to your Twilio phone number.

You will receive an initial response of `Hello, thanks for the new message!`, and any subsequent messages you send will then receive a response of `Your current count is 1`, `Your current count is 2`, and so on.

## Set state attributes

Cookies support several [attributes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_the_lifetime_of_a_cookie), which allow you to define aspects such as duration, security, and more. You can set these using the third parameter to `setCookie`. For example, given the existing call:

```javascript
response.setCookie('count', (count + 1).toString());
```

You could modify the `count` cookie to last for a maximum of 30 minutes (1800 seconds) by setting the `Max-Age` attribute like so:

```javascript
response.setCookie('count', (count + 1).toString(), ['Max-Age=1800']);
```

## Remove or reset state

By default, session cookies persisted by Twilio SMS only last for four hours at most, and you cannot exceed this limit. However, it's perfectly valid to *remove* a cookie at any time to fit your application's needs.

For example, you could clear the count from the previous example once a condition is met, as shown in this sample:

```js title="Clear counter state from an SMS conversation"
// !mark(14,15,16,17,18,19,20,21)
exports.handler = (context, event, callback) => {
  // Initialize a new Response and some TwiML
  const response = new Twilio.Response();
  const twiml = new Twilio.twiml.MessagingResponse();

  // Since we're returning TwiML, the content type must be XML
  response.appendHeader('Content-Type', 'text/xml');

  // Cookies are accessed by name from the event.request.cookies object
  // If the user doesn't have a count yet, initialize it to zero. Cookies are
  // always strings, so you'll need to convert the count to a number
  const count = Number(event.request.cookies.count) || 0;

  if (count > 5) {
    twiml.message("You've reached the end of the count!");
    // In this case we want to remove the count and let the user begin
    // a new conversation
    response.setBody(twiml.toString()).removeCookie('count');
    // Use an early return to respond to the user and avoid other logic paths
    return callback(null, response);
  }

  // Return a dynamic message based on if this is the first message or not
  const message =
    count > 0
      ? `Your current count is ${count}`
      : 'Hello, thanks for the new message! Message again to see your count update.';

  twiml.message(message);

  response.setBody(twiml.toString()).setCookie('count', (count + 1).toString());

  return callback(null, response);
};
```

## Validate state reset

If you save and deploy this new code instead, you should have a very similar interaction with your Twilio phone number. After sending a message, you will receive an initial response, and any subsequent messages you send will then receive a response of `Your current count is 1`, `Your current count is 2`, and so on.

The difference is that after reaching a count of five and sending another message, you'll receive `You've reached the end of the count!`. If you try to message again, you'll find yourself in a completely new conversation. This is a handy way to end interactions, such as if your application has successfully helped a customer, or if your application is a game that the user has won or lost.

> \[!WARNING]
>
> These examples demonstrate adding a single state value as a cookie, but you are free to add more to support your application needs!
>
> Keep in mind, there are [limitations](/docs/serverless/functions-assets/functions/headers-and-cookies/limitations#maximum-header-size) on how many cookies can be set, how large they can be, and how long they can persist until expiring.

> \[!NOTE]
>
> Curious about what else you can build by using cookies to add statefulness to your Functions? Check out this [blog article](/blog/build-wordle-like-sms-game-serverless) to see how you can build your own Wordle clone purely with [Functions](/docs/serverless/functions-assets/functions) and [Assets](/docs/serverless/functions-assets/assets)!
