# Mthimkhulu Welanga Organisation, NPO
A complete website and message inbox for Mthimkhulu Welanga Organisation, NPO —
substance abuse and mental health prevention and support for youth in Bloemfontein.
The site carries the organisation’s **“It’s okay to not be okay”** message, all
programme and contact details, a printable QR code, and three ways for visitors
to reach the team: **appointment request**, **comment**, and **anonymous message**.
A floating **WhatsApp** button is available on every screen.
---
## Start the app
1. Install Python 3 from (tick **Add Python to PATH**).
2. Double-click **`start-app.bat`**.
Or from a terminal in this folder:
```bat
python server.py --port 8090
```
The browser opens automatically.
| What | Address |
| --- | --- |
| Website | |
| Phone on the same Wi-Fi | `http://:8090` — printed when the server starts |
| Private inbox | |
The server prints the LAN address (for example `http://172.20.10.9:8090`) and the
inbox key each time it starts. The key is also stored in `data/admin-key.txt`.
If a phone cannot open the LAN address, allow Python through Windows Firewall
for **private networks** (Windows usually asks the first time the server starts).
### Opening the inbox
Go to `/inbox.html`, paste the key, and press **Unlock**. You can also bookmark:
```text
http://localhost:8090/inbox.html?key=YOUR-KEY
```
The inbox shows appointments, comments and anonymous messages, with filters,
search, refresh and **Export CSV**. Keep the key private: anyone with it can read
the messages. To choose your own key, set an environment variable before starting:
```bat
set MW_ADMIN_KEY=my-long-private-key
python server.py --port 8090
```
---
## How messages are stored
Everything stays on this computer. Messages are appended to
`data/submissions.json`; nothing is sent to a third party.
- **Appointments** store name, phone, optional email, date, time, topic and notes.
- **Comments** store optional name/email and the comment.
- **Anonymous messages** store **only the message** — no name, email, phone, IP
address or device details are saved.
The form also rate-limits submissions (20 per 10 minutes per device) and caps
message sizes.
The server never serves the `data/` folder, hidden files, source files or
directory listings; requests for them return 404. Messages and the inbox key
are only readable through the inbox API with the correct key.
If the page is opened as a plain file (`file://`) or the server is unavailable,
the form falls back to **WhatsApp / email / copy message** so the visitor can
still send it. Anonymous messages never use WhatsApp or email automatically,
because that would reveal the sender — they offer “Copy my message” instead.
---
## QR code
The **QR code** section builds a code in the browser. It defaults to the address
the page is currently open on.
- On `localhost` the code is only useful on this computer. The page warns you.
- To print a poster, **publish the site first**, then paste the public address
into the QR field and press **Generate**.
- **Download PNG** saves a high-resolution code for print; **Print poster**
prints a clean A4 card with the crest.
The site is already published, so `assets/js/config.js` pre-fills the QR code
with the live address:
```js
window.MW_CONFIG = {
apiBase: "",
shareLink: "https://its-okay-to-not-be-okay.pages.dev",
whatsapp: "27825550282",
phone: "+27825550282",
email: "mthiweorg@gmail.com",
address: "Roth Avenue, Willows, Bloemfontein, 9301"
};
```
### Publishing the site
**Short answer:** it is already published, free, on Cloudflare Pages —
. No card, permanent, and it keeps
the private inbox and the anonymous promise. To change the site later, redeploy
with `wrangler` as described below. Setup, database and key details are in
`deploy\cloudflare\README.md`.
The page and QR code work on any static host. The three forms need a small
backend to receive messages; on the live site that is a Cloudflare Function
writing to D1. Running `server.py` locally still works too, and `apiBase` in
`config.js` can point at a different backend if the site ever moves.
Phones scanning a QR code must be able to reach the address in it. A
`localhost` address will not work from a phone; use the LAN address on the same
Wi-Fi, or the live address below.
#### Live site
| What | Address |
| --- | --- |
| Public site (phrase in the address) | |
| Private inbox | `https://its-okay-to-not-be-okay.pages.dev/inbox.html?key=YOUR-KEY` |
| Admin key file | `data/inbox-key.txt` (private, gitignored) |
This is **permanent hosting**, not a tunnel. It stays up when this computer is
off, and the QR code on the page already points at it. Hosting is Cloudflare
Pages with a free D1 database; the address is free because it uses the
`pages.dev` domain, so nothing needs renewing and no card is on file.
The inbox key is the only way into the inbox. It is not in the website code;
it lives in the Cloudflare secret `MW_ADMIN_KEY` and in `data/inbox-key.txt`.
Keep a copy of that file somewhere safe — if it is lost, the inbox can still be
reached by setting a new key in the dashboard and redeploying.
##### Redeploying changes
The project was created by Direct Upload, so Cloudflare's Git integration
cannot be attached to it. Push changes with `wrangler` instead:
```powershell
cd "C:\Users\bceem\OneDrive\Documents\Default Project\deploy\cloudflare"
npx wrangler pages deploy public --project-name its-okay-to-not-be-okay --branch main
```
Copy any edited files from the root site into `deploy\cloudflare\public\` first
(the published copy is the one in that folder). Wrangler writes progress to
stderr, so PowerShell may report exit code 1 even when the deploy succeeded —
check for `Deployment complete!` in the output.
#### Fallback option: Netlify (not used)
`deploy\netlify` is a static copy that needs no server. Nothing was published
there, and it is kept only as a fallback. It has no `inbox.html` or `server.py`;
the three forms go to Netlify Forms, which allows 100 submissions a month.
Full steps are in `deploy\README.md`.
#### Why Cloudflare Pages was chosen
Cloudflare's free plan hosts the site **and** a small database, so messages stay
in your own inbox page with your own key, and the sender's IP is never stored
with a message. The live project is `its-okay-to-not-be-okay`, database
`mw-messages`, and the address is set in the QR code and the page metadata.
Full click-by-click setup, privacy details and free-plan limits are in
`deploy\cloudflare\README.md`.
Both free options cost R0 and need no card:
| | Netlify (fallback) | Cloudflare Pages (live) |
| --- | --- | --- |
| Publish effort | drag and drop | `wrangler pages deploy` |
| Inbox | Netlify dashboard | `inbox.html` with your key |
| Anonymous promise | message only, host logs IP | message only, IP hashed for 10 min in a separate table |
| Monthly limit | 100 submissions | 100,000 requests/day |
---
## Editing the site
| File | What it controls |
| --- | --- |
| `index.html` | All page content: about, programmes, vision, contact details |
| `inbox.html` | Private message inbox |
| `assets/css/styles.css` | Colours, type, spacing, print layout |
| `assets/js/app.js` | Navigation, tabs, validation, sending, QR tool |
| `assets/js/config.js` | Phone, WhatsApp number, email, address, fixed QR link |
| `assets/js/qr.js` | Self-contained QR encoder (no internet needed) |
| `assets/brand/logo.svg` | Designed crest used in the header, hero, QR card and footer |
| `assets/brand/social-card.png` | 1200 × 630 sharing image |
| `favicon.png` | Browser tab icon |
| `server.py` | Website server, message API and inbox data |
| `deploy/netlify/` | Free-hosted copy: same site, Netlify Forms instead of `server.py` |
| `deploy/cloudflare/` | The **live** site plus `functions/` and D1, keeps the private inbox |
Brand colours: ink `#11211d`, teal `#2b5f55`, gold `#b98a34`,
cream `#f7f2e7`. Display type is a Georgia/Palatino serif stack.
### Contact details used on the site
- WhatsApp / phone: **082 555 0282** (`+27 82 555 0282`)
- Email: **mthiweorg@gmail.com**
- Address: **Roth Avenue, Willows, Bloemfontein, 9301** (at National Hospital)
- Registration: **610-1021** · **Reg. No.: 290-537 NPO**
- Office hours: Mon–Thu 8:30am–4pm, Fri 8:30am–2pm, weekends and public holidays closed
---
## Files and housekeeping
- `data/` — private messages and inbox key. **Do not publish this folder.**
It is already listed in `.gitignore`.
- Back up `data/submissions.json` regularly. It is a plain JSON list.
- To clear all messages, stop the server, replace the file contents with `[]`,
and start the server again.
- `start-app.bat` is the double-click launcher; `server.py` is the server.
### Crisis support shown on the page
SADAG national helpline **0800 567 567** (24 hours) and emergency **10111**.
Check these numbers before publishing, as helpline details can change.