# 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 1–3 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 | ~10–15 SEK per request | ~3–5 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 | ~12–18 SEK total (estimated) | Yes | | Strålfors Document Delivery API | Print + envelope + mail, enterprise grade | ~8–15 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: 2–4 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 | 10–15 SEK | 3–5 SEK | | PostNord postage / print+mail | 22 SEK (postage only) | 12–18 SEK | | Stripe processing (~1.5% + 2 SEK) | ~3 SEK | ~3 SEK | | Envelope, paper, printer (manual) | ~5 SEK | 0 SEK (included) | | **Total cost per letter** | **~40–45 SEK** | **~18–26 SEK** | ### Pricing Tiers | Tier | Monthly price | Letters included | Extra letters | Est. margin (Phase 1) | |------|--------------|-----------------|---------------|----------------------| | Pay-as-you-go | — | 0 | 49 SEK | 23–31 SEK | | Basic | 99 SEK/mån | 3 | 39 SEK | 13–21 SEK | | Pro | 199 SEK/mån | 10 | 29 SEK | 3–11 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. ~10–15 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:** ~4–6 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:** ~6–10 weeks (1 person, full-time) **Cost:** API fees, legal consultation (~10,000–30,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*