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¶
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.