Document Index
Market Overview
Doc 01Market Size by Segment
| Segment | Annual Value (Finland) |
|---|---|
| Professional home cleaning | €200–300M |
| Handyman / home repair | €150–200M |
| Moving services | €80–120M |
| Garden / outdoor | €50–80M |
| Other household services | €100–150M |
| Total Addressable Market | €500–800M |
Kotitalousvähennys — The Built-In Demand Subsidy
A direct income tax deduction (not a taxable income reduction — a direct deduction from tax owed) for work performed at a Finnish household. No global competitor has built around it.
| Parameter | Value |
|---|---|
| Deduction rate | 45% of the labor cost |
| Annual cap per person | €2,250 |
| Per household (both spouses) | €4,500 |
| Provider requirement | Must have Finnish Y-tunnus and be tax-registered |
| Where to claim | Annual tax return at vero.fi → Kotitalousvähennys |
| Informal market (cash) | Homora platform |
|---|---|
| No tax receipt | Automatic itemized receipt |
| Customer loses deduction | Customer saves 45% on labor |
| Provider avoids taxes | Provider legally registered (Y-tunnus) |
| No accountability | Reviews, payment protection, recourse |
Current State — HOMORA Service Oy (Feb 2026)
| Signal | Value |
|---|---|
| Legal entity | HOMORA Service Oy, Helsinki · Mäntytie 21, 00270 Helsinki |
| Services live | Cleaning, laundry & ironing, carpet cleaning, handyman, moving, sewing |
| Completed jobs | 120+ cleanings · 80+ laundry jobs |
| On-time rate | 98% |
| Customer satisfaction | 95% |
| Current tech | WordPress contact form — no real marketplace infrastructure |
Competitor Analysis
Doc 02TaskRabbit
True marketplace. Client browses profiles, books instantly. Mandatory background checks. 15% client fee + 15% provider fee.
Thumbtack
Lead-gen marketplace. Pros pay per lead regardless of conversion. SEO dominance ("painter near me"). No transaction fee.
Helpling
Started managed (set prices, dispatched cleaners), pivoted to marketplace. Background checks + liability insurance. Never launched in Finland.
Bark.com
Zero friction for clients (no account required). Pros buy credits to respond to leads. High top-of-funnel but low trust infrastructure.
Feature Matrix — Where Homora Wins
| Feature | TaskRabbit | Thumbtack | Helpling | Bark | Homora |
|---|---|---|---|---|---|
| Instant booking | ✅ | Partial | ✅ | ❌ | ✅ |
| Integrated payment | ✅ | Newer | ✅ | ❌ | ✅ |
| Provider verification | ✅ | Optional | ✅ | ❌ | ✅ Y-tunnus |
| Reviews & ratings | ✅ | ✅ | ✅ | ✅ | ✅ |
| Tax receipts | ❌ | ❌ | ❌ | ❌ | ✅ AUTO-GENERATED |
| Available in Finland | ❌ | ❌ | ❌ | ❌ | ✅ Helsinki-first |
Key Lessons Applied to Homora
| # | Lesson | How Homora applies it |
|---|---|---|
| 1 | Start with one service, one city | Cleaning in Helsinki metro. Handyman in Phase 2. |
| 2 | Avoid the managed model | True marketplace — self-employed providers (toiminimi) with Y-tunnus. No worker classification risk. |
| 3 | Charge the transaction, not the lead | 20% commission on completed bookings. No lead fees. Aligned incentives. |
| 4 | Trust is the conversion unlock | Y-tunnus verification + ID check + reviews. In Finland, trust is not optional. |
| 5 | Tax receipt = Finnish moat | Auto-generated kotitalousvähennys PDF on every completed booking. No competitor does this. |
| 6 | Recurring bookings drive LTV | Recurring booking mechanics in Phase 2 (weekly/bi-weekly cleaning). |
Business Model
Doc 03Commission Structure — MVP
Provider receives: €75 × 80% = €60.00
Homora revenue: €75 × 20% = €15.00
Stripe fee (~1.4% + €0.25): ~€1.30 absorbed by Homora
Homora net: ~€13.70
| Platform | Blended take rate | vs Homora |
|---|---|---|
| Handy | 30–40% | Much higher |
| TaskRabbit | 25–30% | Higher |
| Homora | 20% | Our rate |
| Helpling | 15–25% | In range |
Revenue Projections (Year 1, Helsinki)
| Milestone | Monthly bookings | Monthly GMV | Monthly revenue |
|---|---|---|---|
| Launch | 100 | €12,000 | €2,400 |
| 3 months | 300 | €36,000 | €7,200 |
| 6 months | 600 | €72,000 | €14,400 |
| 12 months | 1,500 | €180,000 | €36,000 |
The Kotitalousvähennys Pricing Angle
This is a pricing strategy, not just a feature. A service that costs €100 "feels" like €55 to a qualifying Finnish household after the tax deduction.
| Standard framing | Homora framing |
|---|---|
| "Cleaning service, €100" | "Cleaning service, €100. Your actual cost: €55" |
| Cleaner charges €30/hr informally | Cleaner charges €40/hr on Homora — client pays effectively €22/hr after deduction |
Phase 2 Revenue Additions
| Revenue stream | Details | Timeline |
|---|---|---|
| Provider subscription | "Homora Pro" €49/month — reduced fees (8%), premium placement, analytics | Month 7+ |
| Featured listings | Providers pay for top-of-search placement | Month 7+ |
| Insurance add-on | €2–5/booking, Finnish insurer partnership, platform takes a cut | Month 10+ |
| B2B channel | Cleaning contracts for property managers, Airbnb hosts, small businesses | Month 10+ |
Product Roadmap
Doc 05Phase 1 — Prove the Loop Weeks 1–8
WEEK 1–2: FOUNDATION
- Supabase setup — auth, tables, RLS policies
- Next.js scaffolding (App Router, Tailwind)
- Provider registration form
- Admin panel skeleton
WEEK 3–4: CORE FLOWS
- Customer homepage + provider search
- Provider profile + booking flow
- Booking confirmation + notifications
- Availability calendar (provider)
WEEK 5–6: MONEY & TRUST
- Stripe PaymentIntent integration
- Provider accept/decline (2h window)
- Stripe Connect payouts
- Tax receipt PDF auto-generation
WEEK 7–8: POLISH & LAUNCH
- Admin: Y-tunnus review, dispute handling
- Email notifications (Resend)
- Mobile-responsive sweep
- Provider onboarding (Abbas: 30 providers)
Phase 2 — Grow the Core Months 3–6
- Recurring booking management (weekly / bi-weekly / monthly)
- Automated Y-tunnus verification via Finnish Business Information System API
- Handyman category — quote-based flow, portfolio photos
- iOS + Android apps (React Native or PWA + Capacitor)
- Provider earnings analytics dashboard
Phase 3 — Expand & Monetize Months 7–12
- City-specific landing pages (SEO) + Tampere/Turku launch
- "Homora Pro" provider subscription — €49/month, 8% commission
- B2B channel — property managers, Airbnb hosts
- Insurance add-on partnership
- Smart matching + dynamic pricing suggestions
Mahdi vs. Abbas — Ownership Split
| Area | Mahdi (Tech) | Abbas (Business) |
|---|---|---|
| Platform development | ✅ All of it | — |
| Provider onboarding (first 30) | — | ✅ Recruit manually |
| Customer acquisition | — | ✅ Marketing, channels |
| Tax receipt format | ✅ Builds | ✅ Confirms with accountant |
| Pricing decisions | Advises | ✅ Owns |
| Category expansion timing | Advises on buildability | ✅ Decides based on demand |
| Partnership deals (IKEA, insurers) | — | ✅ Owns |
Product Requirements (PRD)
Doc 10MVP Scope — In
| Categories | Cleaning, Handyman, Laundry, Moving, Carpet Cleaning |
| Geography | Helsinki metro (Helsinki, Espoo, Vantaa, Kauniainen) |
| Provider req. | Valid Finnish Y-tunnus (mandatory) |
| Booking model | Fixed hourly rate. No bidding or quotes. |
| Payment | Card-only via Stripe. Held until job done. |
| Payouts | Weekly, Monday, Finnish IBAN |
| Tax receipts | Auto-generated PDF, emailed to customer |
| Platform | Web (mobile-responsive). No native app. |
MVP Scope — Out
| Native iOS / Android app | Phase 2 |
| Swedish language | Phase 2 |
| Cities outside Helsinki metro | Phase 3 |
| Background check API | Manual review |
| In-app chat | Phone revealed after booking |
| Subscription plans | Phase 2 |
| Real-time GPS tracking | Not needed |
| Materials cost handling | Labor only |
Success Metrics — 3 Months Post-Launch
| Metric | Target |
|---|---|
| Verified providers live | 20–30 |
| Completed bookings | 100+ |
| Monthly GMV | €8,000–15,000 |
| Tax receipts generated | 100% of completed bookings |
| Repeat booking rate | >25% |
| Provider response rate (2h window) | >85% |
| Platform uptime | >99.5% |
| Customer NPS | >40 |
Open Questions for Abbas
| 1 | How many providers can Abbas onboard on day 1? (Need minimum 5 to look credible) |
| 2 | Do all current providers have Y-tunnus? (Mandatory — no Y-tunnus = not listed) |
| 3 | What are typical hourly rates? (Calibrate the price display and tax calculator) |
| 4 | Who handles customer support at launch? (Abbas solo or shared email?) |
| 5 | Target launch date? (Drives the 6-week build countdown) |
| 6 | Does HOMORA Service Oy have a Stripe account already? |
| 7 | Who absorbs Stripe fees — built into commission or passed to customer? |
Business Rules
Doc 11Provider Eligibility
1234567-8). No Y-tunnus = not listed.pending. Searchable only after admin sets approved.Booking Rules
Cancellation Policy
| Who | When | Outcome |
|---|---|---|
| Customer | >24h before start | Full refund · No fee |
| Customer | ≤24h before start | 50% charged · 50% refunded |
| Customer | After job started | No refund |
| Provider | Any time before job | Full refund to customer. Cancellation count tracked. 3 in 30 days → admin review. |
| Auto-decline | After 2h no response | Full refund · No provider penalty (first time) |
Payout Rules
completed. Admin can also mark complete.Tax Receipt Rules (Finnish Law)
completedlabor_cost × 0.45. Shown as estimate during booking; locked on receipt after completion.Non-Functional Requirements
Doc 12Performance Targets
| Page load (LCP) | <2.5s on 4G mobile |
| Provider list API | <500ms |
| Booking creation | <1s (includes Stripe) |
| PDF generation | <30s (async, emailed) |
| Platform uptime | >99.5% monthly |
Security Requirements
| Auth | Supabase Auth (JWTs, httpOnly cookies) |
| Authorization | Row Level Security on all tables |
| Payment data | Never stored on Homora servers (Stripe PCI-DSS L1) |
| Provider docs | Private Supabase Storage, signed URLs, admin-only |
| HTTPS | Enforced by Vercel. No HTTP fallback. |
GDPR Compliance (Finnish tietosuoja)
| Data | Purpose | Retention |
|---|---|---|
| Customer name, email, phone | Account, notifications | Until deletion request |
| Customer home address | Booking fulfillment | 10 years (tax records) |
| Provider Y-tunnus | Tax receipt, legal compliance | 10 years |
| Provider ID document | Identity verification | 2 years after decision |
| Booking history + receipts | Tax receipts, dispute resolution | 10 years (Finnish accounting law) |
| IP addresses / session logs | Security, fraud prevention | 90 days |
Browser & Device Support
| Platform | Support | Notes |
|---|---|---|
| Chrome (latest 2) | Full | |
| Safari (iOS 15+) | Full | Primary mobile browser in Finland |
| Firefox (latest 2) | Full | |
| Edge (latest 2) | Full | |
| iOS Safari (iPhone SE+) | Full | Min viewport 375px |
| IE11 | Not supported |
MVP Engineering Spec
Doc 04Tech Stack
| Layer | Choice | Why |
|---|---|---|
| Frontend | Next.js (App Router) | Fast build, SEO-friendly, React ecosystem |
| UI | Tailwind + shadcn/ui | Ship fast, accessible, professional |
| Backend / DB | Supabase (Postgres + Auth + Storage) | One service for everything, generous free tier |
| Payments | Stripe (PaymentIntents + Connect) | Standard for EU, handles provider payouts |
| Resend | Simple API, great deliverability | |
| React-pdf | Tax receipt generation in-process | |
| Hosting | Vercel (frontend) + Supabase (backend) | Both have generous free tiers, auto-scaling |
| Maps | Google Maps Places API | Address autocomplete + geocoding |
6-Week Build Sequence
Week 1 — Foundation
Supabase setup, auth tables, RLS, Next.js scaffold, provider registration form, admin skeleton
Week 2 — Provider Side
Provider profile (public), provider dashboard, Stripe Connect onboarding, availability calendar
Week 3 — Customer Side
Homepage + provider list/search, provider profile + "Book now" flow, booking form, price breakdown with kotitalousvähennys estimate
Week 4 — Payments
Stripe PaymentIntent at booking, booking status state machine, provider accept/decline (2h auto-decline), email notifications via Resend
Week 5 — Post-Job
Job completion flow, Stripe payout trigger, tax receipt PDF generation (React-pdf), email receipt, review prompt
Week 6 — Polish & Launch
Y-tunnus validation, mobile sweep, error states, staging smoke test with Abbas, go-live redirect from homora.fi
Database Schema & ERD
Doc 0707-Database-Schema.md. Source of truth for all data decisions.Entity Relationship Diagram
All Tables · All FKsRelationship Matrix
| From | Cardinality | To | Via / Notes |
|---|---|---|---|
auth.users | 1 : 1 | users | id cascade delete — extends Supabase auth with profile data |
users | 1 : 0..1 | provider_profiles | user_id UNIQUE — customer can become a provider; one profile only |
users | 1 : many | bookings | customer_id — all bookings made by this user as a customer |
users | 1 : many | reviews | reviewer_id — reviews written by this user |
users | 1 : many | tax_receipts | customer_id — all receipts for this customer |
users | 1 : many | notifications_log | user_id — audit of all emails/SMS sent |
provider_profiles | 1 : many | provider_documents | provider_id — ID docs, business registration uploads |
provider_profiles | 1 : many | availability_slots | provider_id — the provider's calendar |
provider_profiles | 1 : many | bookings | provider_id — all jobs this provider fulfills |
provider_profiles | 1 : many | reviews | provider_id — all reviews received |
availability_slots | 1 : 0..1 | bookings | booking_id on slot — slot is locked when booking is created |
bookings | 1 : 0..1 | reviews | booking_id UNIQUE — one review per completed booking |
bookings | 1 : 0..1 | tax_receipts | booking_id UNIQUE — one receipt per completed booking |
Enum Reference
| Enum | Values | Used in |
|---|---|---|
user_role | customer · provider · admin | users.role |
verification_status | pending · approved · rejected | provider_profiles.verification_status |
booking_status | pending_provider → confirmed → in_progress → completed · cancelled | bookings.status |
service_category | cleaning · handyman · laundry · moving · carpet_cleaning · other | provider_profiles.categories · bookings.category |
cancellation_reason | customer_cancelled · provider_declined · admin_cancelled · no_show | bookings.cancellation_reason |
payout_status | pending · paid · failed | bookings.payout_status |
Trigger Map
| Trigger | Table | Event | Effect |
|---|---|---|---|
users_updated_at | users | BEFORE UPDATE | Sets updated_at = now() |
provider_profiles_updated_at | provider_profiles | BEFORE UPDATE | Sets updated_at = now() |
bookings_updated_at | bookings | BEFORE UPDATE | Sets updated_at = now() |
reviews_update_rating | reviews | AFTER INSERT | Recalculates provider_profiles.rating_avg and rating_count |
Index Reference
| Index | Table | Type | Purpose |
|---|---|---|---|
idx_provider_categories | provider_profiles | GIN | Filter by service category array |
idx_provider_service_areas | provider_profiles | GIN | Filter by city/area array |
idx_provider_verification | provider_profiles | BTree | Admin review queue + public listing |
idx_provider_y_tunnus | provider_profiles | Unique | Prevent duplicate Finnish biz IDs |
idx_availability_provider_date | availability_slots | BTree | Provider calendar view by date |
idx_availability_available | availability_slots | Partial BTree | Only open slots shown to customers |
idx_bookings_customer | bookings | BTree | Customer booking history (newest first) |
idx_bookings_provider | bookings | BTree | Provider job list by date |
idx_bookings_status | bookings | BTree | Status-based filtering and dashboards |
idx_bookings_payout | bookings | Partial BTree | Payout cron: only pending payouts |
idx_bookings_response_deadline | bookings | Partial BTree | Auto-decline cron: overdue provider responses |
idx_reviews_provider | reviews | BTree | Provider review list (newest first) |
idx_tax_receipts_customer | tax_receipts | BTree | Customer receipt history (newest first) |
Key Computed Values
hourly_rate_snapshot = copied from provider_profiles.hourly_rate at booking creation (immutable)
labor_cost = duration_hours × hourly_rate_snapshot
platform_fee = labor_cost × 0.20 → Homora revenue per booking
total_charged = labor_cost + materials_cost → what customer pays via Stripe
provider_payout = labor_cost × 0.80 → released to provider after job completion
tax_deduction_est = labor_cost × 0.45 → shown pre-booking (estimate only)
deductible_amount = labor_cost × 0.45 → locked onto tax_receipt after completion
cancellation_fee = total_charged × 0.50 if cancelled ≤ 24h before start
= 0 if cancelled > 24h before start
refund_amount = total_charged − cancellation_fee
API Spec
Doc 08Key Endpoints
| Method | Route | Description |
|---|---|---|
| GET | /api/providers | List providers filtered by category, service_area |
| GET | /api/providers/:id | Provider profile + availability |
| GET | /api/providers/:id/availability | Available slots for a given month |
| POST | /api/bookings | Create booking + Stripe PaymentIntent |
| POST | /api/bookings/:id/confirm | Provider confirms → slot locked → customer notified |
| POST | /api/bookings/:id/complete | Mark complete → trigger Stripe capture + payout + PDF |
| POST | /api/providers/onboard | Submit provider application |
| POST | /api/admin/providers/:id/approve | Admin approves provider → profile goes live |
| GET | /api/receipts/:id/pdf | Download tax receipt PDF (signed URL) |
| POST | /api/reviews | Submit review (post-completion only) |
Error Codes
| Code | Meaning |
|---|---|
SLOT_UNAVAILABLE | Chosen time slot was taken between selection and submission |
PAYMENT_FAILED | Stripe PaymentIntent creation or capture failed |
PROVIDER_NOT_APPROVED | Provider's verification_status is not 'approved' |
RESPONSE_EXPIRED | Provider's 2-hour response window has passed |
REVIEW_ALREADY_SUBMITTED | A review already exists for this booking_id |
User Stories Summary
Doc 06P0 Stories — Required for Launch
| ID | Story | Actor |
|---|---|---|
| US-001 | Browse verified cleaning providers in my area | Customer |
| US-002 | See provider's full profile, bio, availability before booking | Customer |
| US-010 | Book a provider for a specific date and time slot | Customer |
| US-011 | See the full price breakdown including kotitalousvähennys estimate before paying | Customer |
| US-012 | Pay by card at booking. Payment authorized, not captured until job complete. | Customer |
| US-020 | Receive a kotitalousvähennys-compatible tax receipt after each completed job | Customer |
| US-030 | Create an account with email and see all my bookings in one place | Customer |
| US-100 | Apply to join Homora with Y-tunnus, photo, ID, and bank account | Provider |
| US-110 | Be notified of new bookings and accept/decline within 2 hours | Provider |
| US-111 | Mark a job as complete to trigger my payout and customer's receipt | Provider |
| US-112 | Set my weekly availability so customers only book available slots | Provider |
| US-120 | See my earnings breakdown and know when I'll be paid | Provider |
| US-200 | Review and approve provider applications (verify Y-tunnus, ID) | Admin |
| US-201 | See all active bookings and handle disputes | Admin |
| US-202 | Trigger weekly payouts for all completed jobs | Admin |
UX Flows
Doc 0909-UX-Flows.md. Below is the critical path summary.Critical Paths
| Flow | Steps | Key edge cases |
|---|---|---|
| Customer books a job | Home → Browse → Profile → Book (4 steps) → Pay → Confirmed | Slot taken between steps; payment failure; provider no-respond |
| Provider gets a booking | Notification → Dashboard → Accept (full address revealed) → Job done → Mark complete | 2h window expiry auto-decline; customer no-show |
| Tax receipt delivered | Job completed → PDF generated → Emailed to customer → Available in My Bookings | PDF generation failure → admin notified, manual within 24h |
| Provider onboarding | Apply (5 steps) → Admin review (24h) → Approved email → Set availability → Live | Y-tunnus duplicate; Stripe Connect incomplete; rejection + resubmit |
| Cancellation | My Bookings → Cancel → Policy check → Stripe refund → Slot released | >24h = full refund; ≤24h = 50% charged |