## Summary Closes #9. - Campaign composer auto-inserts a tracked homepage link (`utm_source=monica`, `utm_medium` = channel, `utm_campaign` = slug of the name) for email, SMS, and postcard QR — no manual UTM paste. - When `SHORTENER_BASE_URL` + `SHORTENER_API_TOKEN` are set, the app mints that long HTTPS URL via `POST /api/links/` on the shortener **API host** and puts the returned `piha.li` / `beta.piha.li` short URL in SMS, email hrefs, and QR codes. Empty env (local) falls back to the long UTM URL. - Live composer resolves shorts through a portal JSON endpoint (login + CSRF). Browser never calls the shortener. ## Secrets (control node, not git) **monica_site** (`~/Documents/secrets/monica_site/`): ``` # prod SHORTENER_BASE_URL=https://shortener.aimloperations.com SHORTENER_API_TOKEN=monica:<secret> # beta SHORTENER_BASE_URL=https://shortener-beta.aimloperations.com SHORTENER_API_TOKEN=monica:<beta-secret> ``` **url_shortening_service** (same secret, named token): ``` SHORTENER_API_TOKENS=monica:<secret> SHORT_ALLOWED_HOSTS=mkdrealtor.com,aimloperations.com ``` Prod public short host: `piha.li`. Beta: `beta.piha.li`. Generate with `python -c "import secrets; print(secrets.token_urlsafe(32))"`. Template port: westfarn/web_django_template#3 ## Test plan - [ ] `cd site && uv run python manage.py test messaging.tests.CampaignUtmLinkTests` - [ ] Composer: type a campaign name — email gets an HTML link, SMS gets a URL, postcard shows a QR - [ ] With shortener env set: SMS/QR show `piha.li` (or `beta.piha.li`); without it, long UTM URL still works - [ ] Copy/download QR into postcard designer - [ ] Secret files have `SHORTENER_*` on both caller and operator sides before beta/prod deploy Reviewed-on: #10
105 lines
3.7 KiB
Markdown
105 lines
3.7 KiB
Markdown
# monica_site
|
|
|
|
Django 6 marketing platform for Monica Dhillon / MKDRealtor.com — public site, realtor
|
|
portal, multi-channel outreach (email + SMS via SMTP2GO, postcards via PCM Integrations), and social
|
|
scheduling (Facebook / Instagram / LinkedIn) with Ollama-assisted drafting.
|
|
|
|
## Stack
|
|
|
|
- Django 6 + `uv`
|
|
- Postgres (prod/beta); SQLite locally when `DATABASE_URL` unset
|
|
- **dj-queue** (Django Tasks backend, Postgres queue, no Redis/Celery)
|
|
- Docker; deploy via `server-infra` Ansible pipeline
|
|
|
|
## Environments
|
|
|
|
| Env | Domain | Port (host) | Gate |
|
|
|-----|--------|-------------|------|
|
|
| prod | `mkdrealtor.com` | 8004 | `SITE_UNDER_CONSTRUCTION=true` (holding page) |
|
|
| beta | `monica-preview.aimloperations.com` | 8014 | full app (`SITE_UNDER_CONSTRUCTION=false`) |
|
|
|
|
## Local development
|
|
|
|
```bash
|
|
uv sync
|
|
cd site
|
|
uv run python manage.py migrate
|
|
uv run python manage.py runserver
|
|
# optional: docker compose up # web + postgres + worker (hot reload)
|
|
```
|
|
|
|
Docker compose mounts `./site` into the container and runs Django `runserver` when
|
|
`DJANGO_ENV=dev` (the default). The **worker** service polls `dispatch_due` every 15s so
|
|
scheduled campaigns and social posts fire without a manual command. Edit Python/templates
|
|
→ auto-reload; no image rebuild.
|
|
Rebuild only when Dockerfile, entrypoint scripts, or deps (`pyproject.toml`, `uv.lock`) change:
|
|
|
|
```bash
|
|
docker compose up --build
|
|
```
|
|
|
|
### Local secrets (SMTP2GO, etc.)
|
|
|
|
Copy `.env.example` → `.env` (gitignored). Compose reads `.env` for email vars.
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
# fill EMAIL_HOST_USER / EMAIL_HOST_PASSWORD
|
|
# for real delivery (not console logs):
|
|
# EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
|
|
docker compose up
|
|
```
|
|
|
|
Without `EMAIL_BACKEND=…smtp…`, mail prints to the web container logs (console backend).
|
|
|
|
### SMTP2GO delivery webhooks
|
|
|
|
See [`site/messaging/README.md`](site/messaging/README.md) for SMTP2GO email/SMS webhooks
|
|
and PCM Integrations postcard designer + webhook setup (Bearer auth, events).
|
|
|
|
Prod compose (`docker-compose.prod.yml`) still uses gunicorn with baked-in image code.
|
|
|
|
## Secret env files (control node)
|
|
|
|
Create on the deploy host:
|
|
|
|
```text
|
|
~/Documents/secrets/monica_site/monica_site_prod.env
|
|
~/Documents/secrets/monica_site/monica_site_beta.env
|
|
```
|
|
|
|
Templates: `.env.prod.example` (full var list). Validate with:
|
|
|
|
```bash
|
|
./scripts/validate-env.sh ~/Documents/secrets/monica_site/monica_site_prod.env
|
|
```
|
|
|
|
Campaign UTM short links (piha.li) need **both** sides:
|
|
|
|
| File | Vars |
|
|
|------|------|
|
|
| `monica_site_prod.env` | `SHORTENER_BASE_URL=https://shortener.aimloperations.com` · `SHORTENER_API_TOKEN=monica:<secret>` |
|
|
| `monica_site_beta.env` | `SHORTENER_BASE_URL=https://shortener-beta.aimloperations.com` · distinct `SHORTENER_API_TOKEN` |
|
|
| `url_shortening_service_prod.env` | `SHORTENER_API_TOKENS=monica:<same-secret>` · `SHORT_ALLOWED_HOSTS` includes `mkdrealtor.com` · `PUBLIC_SHORT_URL=https://piha.li` |
|
|
| `url_shortening_service_beta.env` | matching beta token · `PUBLIC_SHORT_URL=https://beta.piha.li` |
|
|
|
|
Generate the secret with `python -c "import secrets; print(secrets.token_urlsafe(32))"`. Do not put it in git. Call the API host, never `piha.li`, to mint links.
|
|
|
|
## Deploy
|
|
|
|
CI on merge to `main` → tests → `server-infra/scripts/deploy.sh --app monica_site --env prod --ref <sha>`.
|
|
|
|
Start the **dj-queue worker on exactly one host**:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.prod.yml --profile worker up -d
|
|
```
|
|
|
|
## Apps
|
|
|
|
`public` · `accounts` · `dashboard` · `leads` · `contacts` · `analytics` · `messaging` · `social`
|
|
|
|
Social AI drafts: `POST /portal/social/api/generate/` → Ollama at `OLLAMA_BASE_URL` (default `http://10.0.0.128:11434`).
|
|
|
|
See [`docs/monica-site-design.md`](docs/monica-site-design.md) for architecture.
|