Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions optin-builder/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# description: Set your phone number as the username for making edits to your app.
# format: secret
# required: true
ADMIN_PHONE_NUMBER=+61493856711

# description: Set a password for your app. You'll use this later to change your opt-in settings.
# format: secret
# required: true
ADMIN_PASSWORD=rUnn1ng_@r0unD_crazy_styl3

# description: Airtable API key.
# configurable: false
AIRTABLE_API_KEY=

# description: Airtable Base ID.
# configurable: false
AIRTABLE_BASE_ID=

# description: Airtable table name.
# configurable: false
AIRTABLE_TABLE_NAME=

# description: Airtable phone number column name.
# configurable: false
AIRTABLE_PHONE_COLUMN_NAME=

# description: Airtable opt-in column name.
# configurable: false
AIRTABLE_OPT_IN_COLUMN_NAME=

# description: Background color of the page.
# configurable: false
BACKGROUND_COLOR=

# description: Text on the registration button.
# configurable: false
BUTTON_CTA="Join"

# description: Title of the opt-in campaign.
# configurable: false
CAMPAIGN_TITLE=""

# description: Brief description of the opt-in campaign.
# configurable: false
CAMPAIGN_DESCRIPTION=""

# description: Contact information for Terms of Service.
# configurable: false
CONTACT_INFORMATION=""

# description: Public URL to a custom CSS file that will be loaded when the form renders.
# configurable: false
CUSTOM_CSS=

# description: Indicates which type of data storage will be used (Segment, Airtable, or Webhook).
# configurable: false
DATA_SOURCE=

# description: Font color of the page.
# configurable: false
FONT_COLOR=

# description: Public URL to the organization's logo.
# configurable: false
LOGO_URL="/placeholder-icon.png"

# description: Statement of how often the subscriber will receive messages (e.g. 4 msgs / month.).
# configurable: false
MESSAGE_FREQUENCY=""

# description: Keyword that users can text to opt-in to the campaign.
# configurable: false
OPT_IN_KEYWORD=

# description: Link to your organization's privacy policy.
# configurable: false
PRIVACY_POLICY_LINK=""

# description: Segment write key.
# configurable: false
SEGMENT_WRITE_KEY=

# description: Confirmation message sent to user once they have subscribed.
# configurable: false
SUBSCRIBE_CONFIRMATION=

# description: Your Twilio phone number for sending and receiving messages.
# format: phone_number
# required: true
TWILIO_PHONE_NUMBER=+12132635137

# description: Webhook URL to send opt-in events to.
# configurable: false
WEBHOOK_URL=
1 change: 1 addition & 0 deletions optin-builder/.github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refer to [AGENTS.md](../AGENTS.md) for all repo instructions.
96 changes: 96 additions & 0 deletions optin-builder/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Twilio Opt-In Builder

A Twilio Serverless app that provides an embeddable SMS opt-in form for collecting campaign contacts, with a live editor, compliance language generation, and support for Airtable, Segment, or webhook integrations.

## Environment Variables

Copy `.env.example` to `.env`. Never commit `.env`.

```bash
cp .env.example .env
```

| Variable | Where to find | Format |
| -------- | ------------- | ------ |
| `ADMIN_PHONE_NUMBER` | Your personal phone number — used as the settings panel username and to receive config-change notifications | E.164 format: `+15551234567` |
| `ADMIN_PASSWORD` | Choose a strong password — used to authenticate access to the live editor settings panel | Any string |
| `TWILIO_PHONE_NUMBER` | Console → Phone Numbers → Manage → Active Numbers | E.164 format: `+15551234567` |
| `CAMPAIGN_TITLE` | The name/title of your opt-in campaign (required for compliance) | Any string |
| `CAMPAIGN_DESCRIPTION` | Brief description of what messages the subscriber will receive (required for compliance) | Any string |
| `MESSAGE_FREQUENCY` | How often messages are sent, e.g. "4 msgs / month" (required for compliance) | Any string |
| `OPT_IN_KEYWORD` | The SMS keyword the user texts to complete double opt-in | Any single word, e.g. `YES` |
| `SUBSCRIBE_CONFIRMATION` | Message sent to the user after double opt-in is confirmed | Any string |
| `CONTACT_INFORMATION` | Support email or toll-free number for Terms of Service (required for compliance) | Any string |
| `PRIVACY_POLICY_LINK` | URL to your organization's privacy policy (recommended for compliance) | URL |
| `DATA_SOURCE` | Which backend stores opt-ins | `webhook`, `airtable`, or `segment` |
| `WEBHOOK_URL` | URL to receive opt-in events (if `DATA_SOURCE=webhook`) | URL |
| `SEGMENT_WRITE_KEY` | Segment Node.js source write key (if `DATA_SOURCE=segment`) | Any string |
| `AIRTABLE_API_KEY` | Airtable API key (if `DATA_SOURCE=airtable`) | Any string |
| `AIRTABLE_BASE_ID` | Airtable base ID (if `DATA_SOURCE=airtable`) | Starts with `app` |
| `AIRTABLE_TABLE_NAME` | Name of the Airtable table for opt-ins (if `DATA_SOURCE=airtable`) | Any string |
| `AIRTABLE_PHONE_COLUMN_NAME` | Column name for phone numbers in Airtable (if `DATA_SOURCE=airtable`) | Any string |
| `AIRTABLE_OPT_IN_COLUMN_NAME` | Column name for opt-in status in Airtable (if `DATA_SOURCE=airtable`) | Any string |
| `LOGO_URL` | Publicly accessible URL to your organization's logo (optional) | URL |
| `BUTTON_CTA` | Text on the opt-in submit button (optional) | Any string, e.g. `Join` |
| `BACKGROUND_COLOR` | Background color for the signup page (optional) | Hex color, e.g. `#ffffff` |
| `FONT_COLOR` | Font color for the signup page (optional) | Hex color, e.g. `#000000` |
| `CUSTOM_CSS` | URL to a custom stylesheet (optional) | URL |

## Commands

```bash
# Install
npm install

# Run locally (live reload)
npm start

# Deploy to Twilio Serverless
npm run deploy

# Test
npm test

# Run a single test file
npm test -- tests/receive-sms.test.js

# Expose webhooks locally
# Requires ngrok — install and authenticate at https://ngrok.com before running
ngrok http 3000
# Set the resulting URL + /receive-sms as the SMS webhook in Twilio Console for your phone number
```

## Project Structure

- `functions/` — Serverless function handlers (public, protected, private variants)
- `functions/receive-sms.protected.js` — Twilio webhook handler that records opt-ins to the configured data source
- `functions/send-sms.js` — Sends the initial opt-in prompt SMS to a user
- `functions/save-settings.js` — Persists env var settings via Twilio REST API (deployed instances only)
- `assets/index.html` — The embeddable opt-in form and live editor UI
- `tests/` — Jest test suite with rewire-based mocks

## Agent Boundaries

**Always:**
- Confirm `.env` is configured before running any command
- Confirm `ADMIN_PHONE_NUMBER`, `ADMIN_PASSWORD`, and `TWILIO_PHONE_NUMBER` are set — the app cannot start meaningfully without them
- Use the Environment Variables section to guide the user to each credential — don't ask them to find values without direction
- Confirm the app is running before asking the user to test it
- Remind the user that settings saved via the live editor only persist on deployed instances — local dev requires editing `.env` and restarting

**Never:**
- Run the app with missing or placeholder credentials
- Hardcode credentials or phone numbers in source files
- Skip the `cp .env.example .env` step

## Verify It's Working

1. Deploy with `npm run deploy`. Open the URL printed at the end (e.g. `https://[my-runtime-url].twil.io/index.html`) in a browser — you should see the opt-in form.
2. Enter a phone number in the form and submit. The number in `TWILIO_PHONE_NUMBER` (from `.env`) will send an SMS prompt; reply with the keyword in `OPT_IN_KEYWORD` and expect the `SUBSCRIBE_CONFIRMATION` message back.

## Twilio Resources

- [Twilio Console](https://console.twilio.com) — credentials, phone numbers, webhook configuration
- [Twilio Serverless Toolkit docs](https://www.twilio.com/docs/labs/serverless-toolkit)
- [SMS Messaging docs](https://www.twilio.com/docs/sms)
- [Phone Numbers — Active Numbers](https://console.twilio.com/us1/develop/phone-numbers/manage/active)
1 change: 1 addition & 0 deletions optin-builder/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
86 changes: 86 additions & 0 deletions optin-builder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Twilio Opt-In Builder

Easily create an embedable SMS Opt-In form to collect contacts for your campaign.

![Twilio Keyword Screenshot](https://user-images.githubusercontent.com/1418949/121948197-c944dd80-cd0b-11eb-8ce3-53a1405eb557.png)

## Key Features

* Live editor allows you to preview your signup form.
* Customizable logo, content, and colors.
* Generate compliance language and terms of service.
* Track opt-ins via webhook, Segment, or Airtable.
* Supports one-click keyword opt-in on Android and iOS.

## Pre-requisites

1. Install the [Twilio CLI](https://www.twilio.com/docs/twilio-cli/quickstart#install-twilio-cli)
2. Install the [serverless toolkit](https://www.twilio.com/docs/labs/serverless-toolkit/getting-started):

```shell
twilio plugins:install @twilio-labs/plugin-serverless
```
## Setup

3. Clone the repository and `cd` into it:
```shell
git clone git@github.com:cweems/twilio-keyword.git

cd twilio-keyword
```
4. Install dependencies:
```shell
npm install
```
5. Create `.env` file and set environment variables:
```shell
cp .env.example .env
```
> Note: you'll need to set `ACCOUNT_SID`, `AUTH_TOKEN`, `TWILIO_PHONE_NUMBER`, `ADMIN_PHONE_NUMBER`, and `ADMIN_PASSWORD` before deploying. All other environment variables can be set by the opt-in builder. If you're using Quick Deploy, these initial environemnt variables will be set for you.

6. Deploy to Twilio Serverless:
```shell
twilio serverless:deploy

# View your opt-in builder at https://[my-runtime-url].twil.io/index.html
```

7. Optional: develop locally if you want to edit HTML / CSS / JavaScript:
```shell
twilio serverless:start --live
```

### Environment Variable Reference
If you're running this project locally, you'll need to manually set environment variables in your `.env` file. When run on Twilio Serverless, environment variables are automatically set when you click the `Save Settings` button.

To keep your tokens and secrets secure, make sure to not commit the `.env` file in git. When setting up the project with `twilio serverless:init ...` the Twilio CLI will create a `.gitignore` file that excludes `.env` from the version history.

| Environment Variable | Description | Required? |
|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------|
| ACCOUNT_SID | Your Twilio Account SID | Required |
| AUTH_TOKEN | Your Twilio Account Auth Token | Required |
| TWILIO_PHONE_NUMBER | The phone number that you will send SMS opt-ins from. | Required |
| ADMIN_PHONE_NUMBER | Your personal phone number, we use this as a username when you save settings, and also to send you and SMS when your configuration changes. | Required |
| ADMIN_PASSWORD | Your password for updating settings via the live editor. | Required |
| LOGO_URL | A publicly accessible link to your organization's logo. | Recommended |
| CAMPAIGN_TITLE | The title of your opt-in page campaign. It should indicate what kinds of SMS your user is signing up for. | Required for compliance |
| CAMPAIGN_DESCRIPTION | A brief description of your opt-in campaign. What types of messages will the user receive? | Required for compliance |
| MESSAGE_FREQUENCY | The number of messages and cadence that a user will receive messages. | Required for compliance |
| BUTTON_CTA | The text on the button to register for the campaign. | Recommended |
| OPT_IN_KEYWORD | The keyword that the user must respond with for double opt-in. | Required for compliance |
| SUBSCRIBE_CONFIRMATION | The message that the user will receive once they have confirmed using double opt-in. | Required for compliance |
| CONTACT_INFORMATION | Your organization's contact information for compliance purposes (included in the Terms of Service). This could be a support email or toll-free number. | Required |
| CUSTOM_CSS | A link to a custom stylesheet. | Optional |
| BACKGROUND_COLOR | The background color for your signup page. | Optional |
| FONT_COLOR | The font color for your signup page. | Optional |
| PRIVACY_POLICY_LINK | A link to your organization's privacy policy. | Recommended required for compliance |
| DATA_SOURCE | The data storage destination for your opt-ins. Can be `webhook`, `airtable`, or `segment`. | Optional |
| SEGMENT_WRITE_KEY | The write key for your Segment Node.js source. Will be used to track opt-in events. | Optional |
| AIRTABLE_API_KEY | API key for your Airtable base if you are storing opt-ins in Airtable. | Optional |
| AIRTABLE_BASE_ID | Base ID for the Airtable base where you'll be storing opt-ins. | Optional |
| AIRTABLE_TABLE_NAME | Name of the table where you'll be storing your opt-ins. | Optional |
| AIRTABLE_PHONE_COLUMN_NAME | Name of the Airtable column where you'll store the phone numbers of your opt-ins. We'll check this column for existing opt-ins and remove the opt-in if the user responds with a stop word. | Optional |
| AIRTABLE_OPT_IN_COLUMN_NAME | Name of the column where you'll store opt-in status in Airtable. Values can be true / false. | Optional |
| WEBHOOK_URL | URL to a webhook on your own custom app that will receive Opt-In events. | Optional |


Loading