Configuration

Sheetbell reads all of its settings from environment variables — values that live outside the code so you can change them per deployment without editing anything. Locally they go in your .env file; in production you set them in your host’s dashboard (see Deploy).

A single helper, src/lib/config.js, reads these values and applies defaults, so there’s exactly one place that decides what each setting means.

Required settings

You can’t run the app without these:

VariableWhat it is
SLACK_CLIENT_IDYour Slack app’s client ID. Identifies the app during sign-in.
SLACK_CLIENT_SECRETYour Slack app’s secret. Also used to sign the login cookie, so keep it secret.
SLACK_OAUTHThe bot token (xoxb-…) used to post messages.
GOOGLE_SERVICE_KEYThe service-account JSON, as a single line.
SPREADSHEET_IDThe spreadsheet that rows are written to.
SLACK_CHANNEL_IDThe channel that notifications are posted to.

Optional settings

These all have sensible defaults — set them only if you want to change the default behavior:

VariableDefaultWhat it does
WEBHOOK_SECRETunsetSecret token that enables webhook triggers (/api/hooks/<slug>). Webhooks are off until this is set. See Workflows.
SPREADSHEET_ID_TESTfalls back to SPREADSHEET_IDA separate scratch spreadsheet used when not in production.
SLACK_CHANNEL_ID_TESTfalls back to SLACK_CHANNEL_IDA separate channel used when not in production.
APP_NAMESheetbellThe name shown in the navigation bar.
SHEET_TAB_CONVERSATIONSConversationsThe tab name that submissions are appended to.
SHEET_TAB_ELIGIBLEEligibleThe tab name used by contact matching.
ELIGIBLE_ENGAGEMENT_COLUMNLast engagement / who did itHeader text of the column to stamp.
ELIGIBLE_NOTES_COLUMNLast Conversation NotesHeader text of the notes column.
MATCH_SIMILARITY_THRESHOLD0.7How close a fuzzy name match must be (0–1).

Production vs. test

The app keeps a PROD flag. It’s on when any of these is true: the host sets CF_PAGES=1, MODE=production, or Astro’s build is production.

  • When PROD is on, it uses SPREADSHEET_ID and SLACK_CHANNEL_ID.
  • When it’s off (local dev, previews), it prefers SPREADSHEET_ID_TEST and SLACK_CHANNEL_ID_TEST if you set them, otherwise it falls back to the main ones.

This lets you point local development at a throwaway sheet and channel so you don’t spam your real channel while experimenting.

Spreadsheet layout

The Conversations tab (required)

Every submission becomes a new row, written into columns A–E in this order:

ABCDE
TimestampOrganizerContactDateMessage

A header row in row 1 is recommended for humans, but the app finds the next empty row regardless.

The Eligible tab (optional)

This powers the contact-reconciliation feature. It’s a roster of known contacts, one per row, with their name in column A and a header row that includes the two columns named by ELIGIBLE_ENGAGEMENT_COLUMN and ELIGIBLE_NOTES_COLUMN.

When someone submits, the app tries to find the contact in this tab:

  1. First by exact, substring, and token matching on the normalized name.
  2. If that fails, by fuzzy matching — it measures how similar two names are (using Levenshtein distance) and accepts the best match above MATCH_SIMILARITY_THRESHOLD. Raising the threshold makes matching stricter; lowering it makes it more forgiving (and more likely to mismatch).

If a contact is found, the app stamps the engagement column with "<organizer> - <date>" and writes the message into the notes column. If the tab or a matching contact doesn’t exist, this step is simply skipped — the Conversations row and Slack message still happen.

Don’t need a contact roster? Leave the Eligible tab out. Nothing breaks.

A note on secrets

SLACK_CLIENT_SECRET, SLACK_OAUTH, and GOOGLE_SERVICE_KEY are sensitive. Keep them out of git (your .env is already ignored), and in production store them as secrets, not plain variables, where your host offers the distinction.

Note: SLACK_CLIENT_SECRET does double duty — it’s also the key used to sign login-session cookies. If you ever rotate it in Slack, every active session becomes invalid and all users are signed out and must sign in again.