Skip to content

Configuration

noticebox is configured by environment variables. The boxes and forms are in the database, next to the submissions: they are made and changed in the inbox (Add new, each form's Settings tab, the box pages) or over MCP with put_form. A fresh instance starts without forms.

Environment

Every setting is an environment variable, and in a deployment they all live in one .env file next to compose.yaml, which reads it with env_file: .env and lists none of them itself. Start from the repository's .env.example: it lists every variable with what it does, and an unchanged copy keeps the defaults. Keep the file to its owner (chmod 600).

Variable Default Meaning
NOTICEBOX_DB data/noticebox.sqlite (/data/noticebox.sqlite in the image) The SQLite file, with the boxes and forms; created at the first start
NOTICEBOX_CONFIG noticebox.json (/app/noticebox.json in the image) Deprecated, and removed in a later release: the forms file of releases before 0.16, imported once on upgrade (Upgrading)
NOTICEBOX_BASE_URL none; required The public URL: in notifications, sign-in links and MCP tokens
NOTICEBOX_SECRET none; required 32 or more random characters that sign sessions and tokens (openssl rand -base64 48)
NOTICEBOX_ALLOWED_EMAILS empty: nobody Who may sign in: addresses and @domain entries, comma-separated (Sign-in)
NOTICEBOX_SMTP_URL, NOTICEBOX_SMTP_FROM none Where sign-in codes are mailed from (Sign-in)
NOTICEBOX_OIDC_<NAME>_CLIENT_ID, _CLIENT_SECRET, _ISSUER, _LABEL none A provider to sign in with; GITHUB and GOOGLE need no issuer (Sign-in)
NOTICEBOX_UPLOAD_DIR the system's temporary folder Where a post's files wait while it is checked; it needs room for the largest post a form allows
NOTICEBOX_RATE_LIMIT 10 Posts per client address and form in ten minutes; 0 turns it off
NOTICEBOX_TRUST_PROXY on 0 to ignore X-Forwarded-For and take the connecting address
NOTICEBOX_DEV_USER none A fixed name instead of signing in, for development; refused when NODE_ENV=production
PORT 3000 Where it listens
PUID, PGID the owner of the mounted /data (1000 if that is root) Who the server runs as inside the container, so the database belongs to the host user

${VAR} inside a string of a form is stored as written and replaced by the variable's value when the forms are loaded, so topics, webhook URLs and other secrets stay in the environment and out of the database; put those variables in .env too. Saving a form that names an unset variable is refused. If a variable a stored form needs is missing at start, the log says so, and a notification that needs it is skipped and logged each time.

Forms are managed in the inbox

The Settings tab edits every part of a form, and get_form and put_form over MCP read and write it as JSON. That JSON is described here: every change, in the inbox or over MCP, is checked against it as a whole before it is saved, and a mistake is refused with its place named. The example is one form, notefeed-report, as put_form takes it under its name:

{
  "forms": {
    "notefeed-report": {
      "box": "notefeed.me",
      "kind": "report",
      "title": "Reports about content on notefeed.me",
      "strict": true,
      "fields": {
        "read_id": { "type": "hidden" },
        "note_id": { "type": "hidden" },
        "reason": { "type": "select", "label": "Reason", "required": true, "options": ["Illegal content", "Personal data", "Copyright", "Spam", "Other"] },
        "text": { "type": "textarea", "label": "What is wrong", "required": true, "max": 4000 },
        "email": { "type": "email", "label": "Reporter's email" }
      },
      "states": ["open", "in_review", { "name": "acted_on", "closed": true, "reason": true }, { "name": "rejected", "closed": true, "reason": true }],
      "origins": ["https://notefeed.me"],
      "redirect": "https://notefeed.me/report?sent=1",
      "retentionDays": 1,
      "notify": [{ "type": "ntfy", "url": "https://ntfy.sh/${NTFY_TOPIC}" }],
      "link": { "url": "https://notefeed.me/r/{read_id}/{note_id}", "label": "Open the note" }
    }
  }
}

Boxes

A box exists as soon as a form names it. Add new makes an empty one for forms to come, and a box page renames or removes it once it has no forms.

A form

The key is the form's name: lowercase letters, digits, - and _, up to 40 characters, starting with a letter or digit; it is the path of the endpoint, /f/<name>.

Key Default Meaning
kind required report or message: the preset of states a form starts with, see States, reasons and kinds
box forms The group the form is listed under in the sidebar
title the name Shown in the inbox and in notifications
thanks a generic sentence The text of the thank-you page at /f/<name>/thanks
fields none The described fields, see below. A form takes undescribed fields too unless it is strict; a page can describe its own, see Posting from a page
strict false Take only the described fields and refuse any other
states the kind's preset The states, in order, the first being where a submission starts: names, or { "name": …, "closed": true, "reason": true, "deleteAfterMinutes": 0 }; see States, reasons and kinds
origins none: posts from anywhere Origins posts are accepted from, https://example.org each
redirect the thank-you page Where the browser goes after a plain post
retentionDays 180 Days after closing until the personal fields are removed
deleteAfterDays none: the anonymised record stays Days after closing until the submission and its history are deleted entirely; at least retentionDays. See Retention
notify none A list of targets, { "type": "ntfy", "url": … } or { "type": "webhook", "url": … }, see Notifications. The older { "ntfy": …, "webhook": … } object is read too.
link none A URL template over the fields, or { "url": …, "label": … }; see below
github none The repository issues are opened in, "owner/name" or { "repo": …, "labels": [ … ] }; see GitHub issues

A field

The key is the field's name as the page posts it: lowercase letters, digits and _, up to 32 characters; _gotcha is reserved for the honeypot and _test for test posts.

Key Default Meaning
type text text, textarea, select, email, hidden or file
label the name How the inbox labels it
required false A post without it is refused
max 500 text, 10 000 textarea, 254 email, 200 select and hidden Longest accepted value, in characters
options required for select The allowed values
personal true for text, textarea, email and file; false otherwise. An undescribed field is always personal Removed by retention once the submission is closed and past retentionDays
help none A sentence for the person filling the form
accept any A file field's kinds: extensions (.stl) or media types (image/png, image/*)
maxFiles 1 How many files a file field takes in one post
maxMegabytes 10 How large each file may be

Values are trimmed, line ends normalised, control characters dropped; a value of any type but textarea is folded onto one line.

A file field has no text: max doesn't apply, and accept, maxFiles and maxMegabytes belong to it alone. Files are kept in the database with their submission; see Files for posting them and Retention for when they go.

link says where the thing a submission is about can be looked at. It is an http(s) URL with {field} placeholders, each the name of one of the form's fields, filled in percent-encoded. The inbox shows it on the submission, in the table, and the MCP tools carry it as link. The label is the link's text, Open by default. A submission whose placeholder fields are empty, because retention removed them, shows no link; ids are usually personal: false and stay, as hidden fields are by default.