Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ Features:
- API keys via `.streamlit/secrets.toml` or environment variables

### Streamlit Pages
- `pages/verify.py` — Email verification endpoint (`/verify?token=...`)
- `pages/verify.py` — Email verification endpoint (`/verify?token=...`); sends welcome email on confirmation
- `pages/unsubscribe.py` — One-click unsubscribe endpoint (`/unsubscribe?token=...`)
- `pages/impressum.py` — Legal notice (§ 5 DDG)
- `pages/privacy.py` — Privacy policy
Expand All @@ -342,7 +342,8 @@ Supported formats:
4. Jobs already displayed in the UI session are pre-seeded into `job_sent_logs` via `db.upsert_jobs()` + `db.log_sent_jobs()` so the first digest doesn't repeat them
5. `emailer.send_verification_email()` sends a confirmation link via Resend
6. User clicks the link → `pages/verify.py` calls `db.confirm_subscriber()` → sets `is_active=True`, then `db.set_subscriber_expiry()` sets `expires_at = now() + 30 days`
7. If email already active, the form shows "already subscribed" (no re-send)
7. `pages/verify.py` sends a best-effort welcome email via `emailer.send_welcome_email()` (fire-and-forget — failure doesn't affect confirmation)
8. If email already active, the form shows "already subscribed" (no re-send)

### Auto-Expiry
- The 30-day clock starts at **DOI confirmation**, not signup (prevents wasted days while email is unconfirmed)
Expand Down Expand Up @@ -381,9 +382,10 @@ Per-subscriber pipeline, designed to run in GitHub Actions (or any cron schedule
Required env vars: `GOOGLE_API_KEY`, `SERPAPI_KEY`, `SUPABASE_URL`, `SUPABASE_SERVICE_KEY`, `RESEND_API_KEY`, `RESEND_FROM`, `APP_URL`.

### Email Templates (`emailer.py`)
- `send_daily_digest()` — HTML table of job matches with score badges and apply links
- `send_verification_email()` — CTA button linking to the verify page
- Both include an impressum footer line built from `IMPRESSUM_NAME`, `IMPRESSUM_ADDRESS`, `IMPRESSUM_EMAIL` env vars
- `send_daily_digest(user_email, jobs, unsubscribe_url, target_location)` — card-style job listings with score pill badges, location pins, "View Job" CTA buttons, match summary stats (excellent/good counts), and target location in header
- `send_welcome_email(email, target_location, subscription_days, privacy_url)` — sent after DOI confirmation; explains what to expect, subscription duration, and links to privacy policy
- `send_verification_email(email, verify_url)` — CTA button linking to the verify page
- All three include an impressum footer line built from `IMPRESSUM_NAME`, `IMPRESSUM_ADDRESS`, `IMPRESSUM_EMAIL` env vars

---

Expand Down Expand Up @@ -482,7 +484,7 @@ Schema setup: run `python setup_db.py` to check tables and print migration SQL.
| `test_cv_parser.py` (6 tests) | `cv_parser.py` | `_clean_text()` + `extract_text()` for .txt/.md, error cases |
| `test_models.py` (23 tests) | `models.py` | All Pydantic models: validation, defaults, round-trip serialization |
| `test_db.py` (35 tests) | `db.py` | Full GDPR lifecycle: add/confirm/expire/purge subscribers, deactivate by token, data deletion, subscription context, job upsert/dedup, sent-log tracking. All DB functions mocked at Supabase client level |
| `test_emailer.py` (7 tests) | `emailer.py` | HTML generation: job row badges, job count, unsubscribe link, impressum line |
| `test_emailer.py` (22 tests) | `emailer.py` | HTML generation: job row badges/cards/location, job count, match stats, unsubscribe link, target location in header, impressum line, welcome email (location, days, privacy, impressum) |
| `test_app_consent.py` (5 tests) | `app.py` | GDPR consent checkbox: session state persistence, widget key separation, on_change sync |

### Testing conventions
Expand Down
17 changes: 8 additions & 9 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,21 @@ Based on the current state (private repo, hosted on Streamlit Community Cloud) a

### 1.2 — Deploy Daily Digest
- [x] **Set up GitHub Actions cron job** for daily_task.py (e.g., `cron: '0 7 * * *'` UTC)
- [ ] **Add secrets to GitHub Actions** — all required env vars from §10
- [ ] **Test the full digest cycle** — subscribe, verify, receive digest, unsubscribe
- [x] **Add secrets to GitHub Actions** — all required env vars from §10
- [x] **Test the full digest cycle** — subscribe, verify, receive digest, unsubscribe
- [x] **Create a welcome email after successful subscription** — explain what to expect, how to contact support, link to privacy policy, show some example matches
- [x] **Add unsubscribe link to digest emails** — include unique tokenized URL to securely identify subscriber without exposing email
- [x] **Make digest email prettier** — use HTML formatting, add Stellenscout logo, style job listings for better readability

### 1.3 — UX Quick Wins
- [ ] **Personalize the UI** — greet user by first name extracted from CV profile
- [ ] **Add "Edit Profile" step** — let user tweak skills/roles/preferences before searching (this is already in Open Issues)
- [ ] **Add a "Preferences" text input** — free-form like *"I want remote fintech jobs, no big corporations"* → append to Headhunter prompt
- [ ] **Add a "Preferences" text input** — free-form like *"I want remote fintech jobs, no big corporations"* → append to profile prompt
- [ ] **Show job age warning** — if `posted_at` is >30 days, badge it as "possibly expired"
- [ ] **Improve job cards** — show apply links more prominently, add company logos via Clearbit/Logo.dev
- [ ] **Add digest preferences UI** — allow users to change `min_score` and cadence (daily/weekly) after subscription

### 1.4 — Monitoring & Observability
- [ ] **Add structured logging** — replace `print()` with `logging` module, include run IDs
- [ ] **Track pipeline metrics** — jobs found per query, avg scores, API latency, cache hit rates
- [ ] **Set up error alerting** — GitHub Actions failure notifications (email or Slack webhook)
- [ ] **Add cost dashboard** — track daily SerpAPI + Gemini usage and estimated monthly spend
- [ ] **Remove random jobs from homepage before CV is entered** — show a friendly welcome message instead of empty job cards
- [ ] **Add filter/sort options for publishing date and score** — both in the digest email and on the homepage after search

---

Expand Down
8 changes: 7 additions & 1 deletion daily_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ def main() -> int:
"company": ej.job.company_name,
"url": _job_url(ej),
"score": ej.evaluation.score,
"location": ej.job.location,
}
for ej in good_matches
]
Expand All @@ -233,7 +234,12 @@ def main() -> int:

log.info(" sub=%s — sending %d matches (score >= %d)", sub_id, len(email_jobs), sub_min_score)
try:
send_daily_digest(sub_email, email_jobs, unsubscribe_url=unsubscribe_url)
send_daily_digest(
sub_email,
email_jobs,
unsubscribe_url=unsubscribe_url,
target_location=sub.get("target_location", ""),
)
except Exception:
log.exception(" sub=%s — failed to send daily digest, continuing", sub_id)

Expand Down
Loading