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

27 KiB
Raw Blame History

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.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
  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