bilhej/REQUIREMENTS.md
Joakim Mörling 255095e6bd Document kontakt@bilhej.se receiving and fix stale contact address in requirements.
- Add production checklist section for Resend inbound on bilhej.se
- Note that mail is read in the Resend dashboard unless a webhook is added later
- Update GDPR letter footer example in REQUIREMENTS.md to kontakt@bilhej.se

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 12:59:49 +02:00

587 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# BilHej / Bilhälsning.se — Requirements Document
> **Note:** This file is loaded by OpenCode via `AGENTS.md` as an external
> instruction source. It is referenced by `opencode.json` instructions.
> **Version:** 0.1.0 (Draft)
> **Date:** April 2026
> **Status:** Pre-development
---
## 1. Executive Summary
BilHej (Bilhälsning.se) is a web platform that allows Swedish residents to contact the owner of a vehicle by entering a registration number, composing a message, and having a physical letter printed and mailed. The platform acts as a trusted intermediary — the user never sees the recipient's name or address. Revenue comes from charging users per letter, with a hybrid subscription + pay-as-you-go model.
---
## 2. Project Vision
A driver sees a car parked on the street. They want to:
- Compliment the owner on their well-kept Volvo 240
- Express interest in buying the car
- Let the owner know their tire is flat or lights were left on
- Give feedback about driving behavior or a honking incident
They visit Bilhälsning.se on their phone, enter the plate, write a short message, pay 49 SEK — and a physical letter arrives in the owner's mailbox 13 days later. The sender remains anonymous unless they choose to include their contact details in the letter body.
---
## 3. Target Users
- **Sender:** Swedish resident, 18+, with email and a payment method
- **Recipient:** Swedish vehicle owner (never directly interacts with the platform)
---
## 4. Data Flow & Privacy Design
```
Sender BilHej Third Parties
────── ────── ─────────────
Enters plate ──────────→ Plate number stored
Composes letter ───────→ Letter content stored
Pays (Stripe/Swish) ───→ Payment recorded
├─→ Transportstyrelsen: owner address lookup
│ (address used to mail envelope, then DELETED)
├─→ PostNord/Strålfors: letter content + address
│ (print, envelope, mail, return tracking ID)
Receives confirmation ←── Tracking + order status
```
**Key principles:**
- The sender never sees the recipient's name or address
- The recipient's address is only used transiently — obtained, used for envelope addressing, then deleted
- The recipient's name is never stored by BilHej
- Letter content is stored only for order history and moderation purposes
- The letter itself serves as GDPR Art. 14 notification (informs recipient their address was accessed)
---
## 5. Functional Requirements
### F1 — User Account (Sender)
| Detail | Description |
|--------|-------------|
| Registration | Email + password |
| Login | Email + password |
| Account page | View order history, manage subscription |
| Auth method | JWT tokens (Spring Security + stateless sessions) |
**Rationale:** BankID is not required. The user never accesses personal data — BilHej
(the company) is the sole data controller querying Transportstyrelsen. Email+password
keeps the user experience simple and avoids BankID integration costs and GDPR
complexity on the user side.
### F2 — License Plate Input
| Detail | Description |
|--------|-------------|
| Input | Registration number (e.g., "ABC123") |
| Validation | Swedish plate format (3 letters + 3 digits, or 3 letters + 2 digits + 1 letter) |
| Lookup — public vehicle info | Transportstyrelsen's open vehicle data (make, model, year, color). This is publicly available and free/low-cost. |
| Lookup — owner address | Transportstyrelsen "Fråga om fordonsägare" or "Fordonsregisterkund" API. NOT shown to the sender. Used internally to address the envelope. |
**Phase 0 (hobby/manual):** The vehicle info lookup can optionally use a free/open dataset or the Transportstyrelsen SMS service for make/model. Owner address is obtained manually via the "Fråga om fordonsägare" form.
**Phase 1 (business/automated):** Direct API integration with Transportstyrelsen as a "Fordonsregisterkund" for automated owner address lookup.
### F3 — Letter Composer
| Detail | Description |
|--------|-------------|
| Template selection | Choose a pre-written template or "Fritt meddelande" |
| Text editor | Textarea with character limit (e.g., 1000 chars) |
| Preview | Rendered preview showing exactly what the recipient will see (A4 page) |
| Sender identity | Optional: include name/email/phone in letter body. Default: anonymous |
| Language | Swedish (expandable to English later) |
### F4 — Letter Templates
| # | Template name | Purpose | Legal risk |
|---|---------------|---------|------------|
| 1 | **Komplimang** | Compliment on the car | Low |
| 2 | **Jag vill köpa din bil** | Purchase interest inquiry | Low |
| 3 | **Tips / servicebehov** | Heads-up about flat tire, lights, etc. | Low |
| 4 | **Synpunkter på körbeteende** | Feedback about driving behavior | **Medium** |
| 5 | **Tuta / frustration** | Honking incident follow-up (worded diplomatically) | **Medium** |
| 6 | **Fritt meddelande** | Free text, sender's own words and responsibility | Low (user liability) |
Templates 4 and 5 should always include clear disclaimers and respectful wording.
If legal counsel advises against them, merge them into "Fritt meddelande" where
the user assumes full responsibility for content.
### F5 — Payment Wall
| Detail | Description |
|--------|-------------|
| Triggers | After letter is composed and sender clicks "Send" |
| Before payment | Sender sees: plate number, vehicle info (if available), letter preview |
| After payment | Letter is queued for processing and dispatch |
| Payment provider | Stripe (cards + Swish) |
| Price tiers | See Section 10 |
### F6 — Letter Sending & Tracking
| Detail | Description |
|--------|-------------|
| Phase 0 (manual) | Owner address obtained via Transportstyrelsen form → manual print + envelope + PostNord "Digitalt frimärke" (22 SEK, no tracking) or Varubrev (22 SEK, tracked) → manual status update |
| Phase 1 (automated) | PostNord "Skicka Direkt" API or Strålfors API → print + envelope + mail + automatic tracking |
| Tracking (automated) | PostNord returns shipment ID → poll for status (CREATED → IN_TRANSIT → DELIVERED) |
| Tracking (manual) | Varubrev shipment ID entered manually |
| User-facing status | "Skickat", "På väg", "Levererat" |
### F7 — Order History
| Detail | Description |
|--------|-------------|
| Access | Authenticated users only |
| Contents | Date, plate number, template used, status, tracking link (if available) |
| Letter content | Stored for sender's reference |
| No PII | Recipient address/name NEVER shown, NEVER stored after sending |
### F8 — Admin Panel
| Detail | Description |
|--------|-------------|
| Access | Internal only (admin credentials) |
| Template management | Create/edit/disable letter templates |
| Order overview | View all orders, statuses, revenue |
| Moderation queue | Review "Fritt meddelande" content if flagged |
| Blocklist | Manage opt-out list (recipients who don't want letters) |
| Revenue dashboard | Stripe integration for transaction overview |
---
## 6. Non-Functional Requirements
| ID | Requirement | Detail |
|----|-------------|--------|
| N1 | Responsive design | Mobile-first. Most users will be on a phone, standing next to a car. |
| N2 | Page load time | <2 seconds on 4G mobile |
| N3 | Security | HTTPS, password hashing (bcrypt), JWT expiration, input sanitization |
| N4 | Privacy | Address deleted after PostNord dispatch. No PII logs. |
| N5 | Availability | Phase 0: best-effort (home server). Phase 1: 99.5% uptime |
| N6 | Localization | Swedish UI (expandable to English) |
| N7 | Audit log | Every address lookup logged with plate, timestamp, purpose. Required by Transportstyrelsen for API users. |
---
## 7. Technical Architecture
### Stack
| Layer | Technology |
|-------|-----------|
| Frontend | Vue.js 3 (Composition API), Vite, Pinia state management, Vue Router |
| Backend API | Java 21, Spring Boot 4, Spring Security (JWT), Spring Data JPA |
| Database | PostgreSQL 16 |
| Deployment | Docker, Docker Compose |
| Hosting (Phase 0) | Home server via dynamic DNS or static IP, Let's Encrypt SSL |
| Hosting (Phase 1) | Swedish VPS provider (Binero, FS Data, Elastx) |
| Payment | Stripe (Checkout / Payment Intents) supports Swish + cards |
| Mail API (Phase 1) | PostNord "Skicka Direkt" or Strålfors Document Delivery API |
### Architecture Diagram
```
┌──────────────────────────────────────────┐
│ Internet │
│ │
│ ┌──────────────┐ ┌───────────────┐ │
│ │ User Phone │ │ User Desktop │ │
│ └──────┬───────┘ └───────┬───────┘ │
└─────────┼─────────────────────┼──────────┘
│ HTTPS │
┌─────────▼─────────────────────▼──────────┐
│ Nginx (reverse proxy) │
│ Let's Encrypt SSL │
└──────────────────┬───────────────────────┘
┌──────────────────▼───────────────────────┐
│ Vue.js 3 SPA (Vite) │
│ Port: 3000 (dev) │
│ Served as static files │
└──────────────────┬───────────────────────┘
│ REST API calls
┌──────────────────▼───────────────────────┐
│ Spring Boot 4 (Java 21) │
│ Port: 8080 │
│ ┌────────────┐ ┌────────────────────┐ │
│ │ Spring │ │ Service Layer │ │
│ │ Security │ │ - LetterService │ │
│ │ (JWT) │ │ - LookupService │ │
│ │ │ │ - MailService │ │
│ │ │ │ - PaymentService │ │
│ └────────────┘ └────────┬───────────┘ │
│ │ │
│ ┌────────────▼───────────┐ │
│ │ PostgreSQL 16 │ │
│ │ Port: 5432 │ │
│ └────────────────────────┘ │
└──────────────────┬───────────────────────┘
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌──────────┐
│ Trans- │ │ PostNord │ │ Stripe │
│ port- │ │ / │ │ (cards + │
│ styrel │ │ Strålfors│ │ Swish) │
│ sen │ │ API │ │ │
└────────┘ └──────────┘ └──────────┘
```
### Database Schema (Core Tables)
```
users
id UUID PK
email VARCHAR(255) UNIQUE NOT NULL
password_hash VARCHAR(255) NOT NULL
created_at TIMESTAMP
subscription ENUM('none', 'basic', 'pro')
orders
id UUID PK
user_id UUID FK → users
plate VARCHAR(10) NOT NULL
template VARCHAR(50)
letter_text TEXT NOT NULL
status ENUM('pending_payment','paid','lookup_started','sent','delivered','failed')
amount_paid DECIMAL(10,2)
tracking_id VARCHAR(100)
created_at TIMESTAMP
updated_at TIMESTAMP
templates
id UUID PK
name VARCHAR(100)
body_template TEXT
is_active BOOLEAN
blocklist
id UUID PK
plate VARCHAR(10) UNIQUE
reason VARCHAR(255)
created_at TIMESTAMP
audit_log
id UUID PK
plate VARCHAR(10)
action VARCHAR(50)
timestamp TIMESTAMP
ip_address VARCHAR(45)
```
---
## 8. API Integrations
### 8.1 Transportstyrelsen
| Aspect | Phase 0 (Hobby) | Phase 1 (Business) |
|--------|----------------|-------------------|
| Method | "Fråga om fordonsägare" form (manual) | "Fordonsregisterkund" API (automated) |
| What you get | Owner name + address | Owner name + address (programmatic) |
| Cost | ~1015 SEK per request | ~35 SEK per query (estimated) |
| Turnaround | Days (manual processing) | Seconds (API) |
| Setup | None (open to individuals) | Formal application + agreement required |
| Name/address stored by BilHej | No (deleted after envelope is addressed) | No (deleted after envelope is addressed) |
**Open questions for Transportstyrelsen:**
- Can a small business / enskild firma get API access, or do they require a minimum volume?
- What is the actual per-query cost?
- What are the formal GDPR/data processing agreement requirements?
- How does the "Fordonsregisterkund" API handle address-update frequency (dagsaktuella uppgifter)?
**Email template for Transportstyrelsen:**
> Hej,
> Jag planerar att starta en tjänst där privatpersoner kan skicka brev till fordonsägare
> via registreringsnummer. Tjänsten fungerar så att avsändaren aldrig ser mottagarens
> adress — det är endast vi som mellanhand som hanterar adressen för att posta brevet,
> varefter adressen raderas.
> Jag undrar:
> 1. Kan vi som mindre företag få tillgång till ägaruppgifter via ert API
> (Fordonsregisterkund)?
> 2. Vad kostar det per uppslag?
> 3. Vilka krav ställer ni gällande GDPR och personuppgiftshantering?
> 4. Finns det någon minimivolym?
> Tack på förhand.
### 8.2 PostNord / Strålfors
| Service | Description | Cost (est.) | Tracking |
|---------|-------------|------------|----------|
| PostNord "Digitalt frimärke" | Buy postage digitally, print and mail yourself | 22 SEK (50g letter) | No |
| PostNord Varubrev 1:a klass | Tracked letter, postage only | ~22 SEK (50g) | Yes (basic) |
| PostNord "Skicka Direkt" API | Print + envelope + mail, fully automated | ~1218 SEK total (estimated) | Yes |
| Strålfors Document Delivery API | Print + envelope + mail, enterprise grade | ~815 SEK total (estimated) | Yes (webhook) |
PostNord API access is free. You only pay for postage/services.
**Open questions for PostNord:**
- What is the actual price for "Skicka Direkt" (print + mail) for a single-page A4 letter?
- Is there a minimum volume?
- Can we get a business account as an enskild firma?
### 8.3 Stripe
| Detail | Description |
|--------|-------------|
| Products | Stripe Checkout, Payment Intents |
| Payment methods | Cards (Visa, Mastercard) + Swish |
| Fees | ~1.5% + 2 SEK per transaction |
| Subscription support | Stripe Billing for recurring subscriptions (Phase 1) |
---
## 9. User Flow
```
Sender visits Bilhälsning.se
┌──────────────┐
│ Enter plate │ "ABC 123"
│ number │
└──────┬───────┘
┌──────────────┐
│ Vehicle info │ "Volvo V70, 2009, Silver"
│ displayed │ (public data, free)
└──────┬───────┘
┌──────────────┐
│ Choose │ Template dropdown or free text
│ template │
└──────┬───────┘
┌──────────────┐
│ Compose/edit │ Text editor + preview
│ letter │
└──────┬───────┘
┌──────────────┐
│ Preview + │ "Så här ser brevet ut"
│ confirm │ Total: 49 SEK
└──────┬───────┘
┌──────────────┐
│ Log in / │ Email + password
│ Register │ (or skip: guest checkout)
└──────┬───────┘
┌──────────────┐
│ Pay via │ Stripe Checkout
│ Stripe/Swish │ (card or Swish)
└──────┬───────┘
┌──────────────┐
│ Confirmation │ "Ditt brev är på väg!"
│ + tracking │ Estimated delivery: 24 days
└──────────────┘
```
**Behind the scenes (after payment):**
```
BilHej backend → Transportstyrelsen lookup (owner address)
→ PostNord/Strålfors API (print + mail)
→ Address deleted from BilHej
→ Order status updated
→ Tracking webhook/callback → status updated
```
---
## 10. Business Model & Pricing
### Cost Analysis (per letter)
| Cost item | Phase 0 (manual) | Phase 1 (automated) |
|-----------|-----------------|--------------------|
| Transportstyrelsen address lookup | 1015 SEK | 35 SEK |
| PostNord postage / print+mail | 22 SEK (postage only) | 1218 SEK |
| Stripe processing (~1.5% + 2 SEK) | ~3 SEK | ~3 SEK |
| Envelope, paper, printer (manual) | ~5 SEK | 0 SEK (included) |
| **Total cost per letter** | **~4045 SEK** | **~1826 SEK** |
### Pricing Tiers
| Tier | Monthly price | Letters included | Extra letters | Est. margin (Phase 1) |
|------|--------------|-----------------|---------------|----------------------|
| Pay-as-you-go | | 0 | 49 SEK | 2331 SEK |
| Basic | 99 SEK/mån | 3 | 39 SEK | 1321 SEK |
| Pro | 199 SEK/mån | 10 | 29 SEK | 311 SEK |
### Example Economics (Phase 1, Pro subscriber sending 15 letters/month)
```
Revenue: 199 (subscription) + 5 × 29 (extra letters) = 344 SEK
Costs: 15 × ~22 SEK = 330 SEK
Gross margin: 14 SEK
(Low margin on Pro — designed for high-volume users, profit on Basic/Pay-as-you-go)
```
---
## 11. Legal & Compliance
### 11.1 GDPR Applicability
| Question | Answer |
|----------|--------|
| Does GDPR apply to a hobby project? | Technically yes if you process personal data of others. **However**, Art. 2(2)(c) exempts "purely personal or household activity." A paid public-facing website is **not** exempt. In practice, enforcement risk at small scale is low, but you should be aware. |
| Does GDPR apply to a registered company? | Yes, unequivocally. |
| Is a license plate personal data? | Yes (it directly identifies a vehicle owner). |
| Is an address personal data? | Yes. |
| What if we only process address transiently? | Data minimization is a GDPR principle (Art. 5(1)(c)). Transient processing with immediate deletion is a strong compliance posture. |
| Do we need to inform the recipient? | Yes, GDPR Art. 14 requires informing the data subject. The letter itself can serve this purpose include a footer like: _"Detta brev skickades via BilHej.se. Din adress hämtades från Transportstyrelsens fordonsregister och har raderats efter utskick. För frågor: kontakt@bilhej.se"_ |
### 11.2 Transportstyrelsen Access
| For individuals | "Fråga om fordonsägare" form open to anyone with a stated reason. ~1015 SEK. |
| For businesses | "Fordonsregisterkund" API requires formal application, stated purpose, data processing agreement. Price not public. |
### 11.3 Blocklist / Right to Object
Under GDPR Art. 21, data subjects have the right to object to processing. BilHej must provide:
- An email/contact form where a recipient can say "don't send me letters"
- A blocklist (per plate) that prevents future letters to that vehicle
- A clear note in the letter footer with opt-out instructions
### 11.4 Recommended Legal Steps
1. **Before launch (Phase 0):** Read Transportstyrelsen's terms for "Fråga om fordonsägare." Include GDPR Art. 14 notice in letter footer. Set up blocklist. Keep address deletion strict.
2. **Before Phase 1:** Consult a Swedish IT-jurist. Register company. Formal data processing agreement with Transportstyrelsen. Appoint DPO if needed. Draft privacy policy and terms of service.
### 11.5 Company vs Private Individual
If the website makes money, you are conducting economic activity. The Swedish Tax Agency (Skatteverket) will consider this a business regardless of formal registration. You can:
- Register as **enskild firma** (sole trader) simplest, personal tax number, unlimited liability
- Register as **aktiebolag (AB)** limited liability, requires 25,000 SEK share capital, more administration
- Not register at all (Phase 0) only viable at very small scale. VAT (moms) becomes an issue above 80,000 SEK/year.
For Phase 0 with manual processing, staying unregistered is workable. If revenue approaches 80,000 SEK/year or if you want automated API access, incorporation will be necessary.
---
## 12. Development Phases
### Phase 0 — MVP (Hobby/Manual)
| Feature | Description |
|---------|-------------|
| Vue.js landing page | Plate input, letter composer, template selection |
| Email+password auth | User registration and login |
| Stripe integration | One-time payments only (no subscriptions) |
| Letter queue | Backend stores orders in DB, admin manually processes |
| Order history | Users see past orders and status |
| Admin panel | Mark orders as processed, enter tracking IDs manually |
| No API integrations (yet) | Address lookup done manually by you |
| Home server deployment | Docker Compose, dynamic DNS, Let's Encrypt |
**Timeline:** ~46 weeks (1 person, part-time)
**Cost:** ~0 SEK (excluding subscriptions/services)
### Phase 1 — Automated
| Feature | Description |
|---------|-------------|
| Transportstyrelsen API integration | Automated owner address lookup |
| PostNord/Strålfors API integration | Automated print + mail + tracking |
| Subscription management | Stripe Billing, recurring payments |
| Automatic tracking | Webhook callbacks from PostNord |
| Blocklist system | Opt-out management |
| Moderation queue | Review flagged free-text letters |
| Company registration + legal | Formal agreements, privacy policy, terms |
| Production hosting | Swedish VPS |
**Timeline:** ~610 weeks (1 person, full-time)
**Cost:** API fees, legal consultation (~10,00030,000 SEK), VPS (~100 SEK/mån)
### Phase 2 — Growth
| Feature | Description |
|---------|-------------|
| Advanced templates | Rich text, optional images (car photo) |
| Analytics dashboard | Revenue, letter volume, user retention |
| Referral system | "Refer a friend, get a free letter" |
| English language support | UI and template localization |
| Mobile app (PWA) | Progressive Web App for app-like experience |
| Partner integrations | Dealerships, insurance companies, etc. |
---
## 13. Risks & Mitigations
| Risk | Severity | Mitigation |
|------|----------|------------|
| Transportstyrelsen denies API access | High | Phase 0 manual fallback works with form requests. SMS service as partial alternative. Investigate alternative data sources (fordonsfraga.se, biluppgifter.se). |
| High per-letter costs kill margins | Medium | Start with pay-as-you-go at 49 SEK (margin exists even at Phase 0 costs). Move to subscription+automation to reduce cost base. |
| Recipient complaints / negative PR | Medium | Polite, respectful templates. Clear opt-out in every letter. Blocklist system. Avoid "Tuta" template unless diplomatically worded. |
| GDPR complaint to IMY | Low (Phase 0) / Medium (Phase 1) | Address deletion, no name storage, Art. 14 notice in letter, blocklist. Legal review before Phase 1. |
| Home server downtime | Low | Phase 0 is best-effort. Not critical for low volume. |
| Stripe account closure (reputation risk) | Medium | Ensure terms of service compliance. Have backup payment method. |
| Competition | Low | No known competitor offers this exact service (physical letter via plate lookup). Car.info / biluppgifter.se show vehicle info but don't facilitate contact. |
---
## 14. Open Questions
| # | Question | Owner | Priority |
|---|----------|-------|----------|
| Q1 | What is Transportstyrelsen's actual per-query price for "Fordonsregisterkund" API? | To investigate | High |
| Q2 | Can we use "Fråga om fordonsägare" form for multiple requests as a commercial service? | To investigate | High |
| Q3 | What is PostNord "Skicka Direkt" API pricing for print-and-mail? | To investigate | High |
| Q4 | Does the SMS service give enough info to derive or supplement address data? | To investigate | Medium |
| Q5 | What are the exact legal requirements for API access company registration? DPO? | To investigate | High |
| Q6 | Should we include the sender's name by default or default to anonymous? | Product decision | Low |
| Q7 | Should "Fritt meddelande" be moderated (reviewed before dispatch) or sent as-is? | Product decision | Medium |
| Q8 | Should we charge VAT (moms) from day one or only above 80,000 SEK? | Tax question | Medium |
---
## 15. Tech Stack Summary
```
Frontend: Vue.js 3, Vite, Pinia, Vue Router
Backend: Java 21, Spring Boot 4, Spring Security (JWT), JPA/Hibernate
Database: PostgreSQL 16
Deploy: Docker, Docker Compose, Nginx reverse proxy
Hosting: Home server (Phase 0) → Swedish VPS (Phase 1)
Payments: Stripe (cards + Swish)
Mail: Manual (Phase 0) → PostNord API / Strålfors API (Phase 1)
Vehicle: Manual form (Phase 0) → Transportstyrelsen API (Phase 1)
SSL: Let's Encrypt (Certbot)
CI/CD: GitHub Actions (optional)
```
---
## 16. Next Actions
1. **Email Transportstyrelsen** Ask about API access, pricing, requirements
2. **Email PostNord** Ask about "Skicka Direkt" print-and-mail pricing
3. **Set up development environment** Docker Compose with Vue.js + Spring Boot + PostgreSQL
4. **Build plate input + vehicle info display** First frontend component
5. **Build letter composer + template system** Core user interaction
6. **Integrate Stripe Checkout** One-time 49 SEK payment
7. **Build admin panel** Order management, manual status updates
8. **Deploy to home server** Docker Compose, Nginx, Let's Encrypt
9. **Test with one real letter** End-to-end: plate compose pay manual lookup mail track
10. **Decide: incorporate or stay manual** Based on demand
---
*End of Requirements Document*