Skip to main content

Public Report Page

The 30-second tenant flow is the single most important user experience in PMFriend. If tenants can't submit in under a minute, they call instead, and the whole triage flow collapses.

The URL

Every property has a stable token (UUID, never rotates) at:

https://app.pmfriend.com/report/{token}

You generate the URL once when the property is created, print it onto a QR sticker, stick it inside the fridge door or meter box. It's the only interface the tenant ever uses.

What the tenant sees

PMFriend — 45 Bourke Street, Melbourne

What's going on?
┌─────────────────────────────────────┐
│ Tell us what's happening. A few │
│ sentences is plenty. │
│ │
│ │
└─────────────────────────────────────┘

Photos (optional, up to 3)
┌──────┐ ┌──────┐ [+ Add another]
│ IMG │ │ IMG │
└──────┘ └──────┘

Your name [_______________________]
Email or mobile [_____________________] (we'll only use this to
get back to you)

[ Submit ]

Is it an emergency (gas, fire, flood, personal safety)?
Call 000 or your after-hours number immediately instead.

One textarea + optional photos + name + contact. No address dropdown (the URL already encodes the property). No category picker (Claude handles that, and it reads any attached photos alongside the text).

What happens on submit

  1. Creates a MaintenanceRequest with channel=WEB_FORM, status NEW.
  2. If the contact email matches an existing tenant record, links to it. Otherwise creates a new tenant.
  3. Each attached photo (max 3, ≤ 8 MB each, JPEG/PNG/WEBP/HEIC) is stored via DocumentStorage (S3 in prod, filesystem in dev) and an index row is written to maintenance_request_photos.
  4. Fires a Hibernate post-insert event → PMFriend notification feed shows it immediately.
  5. The PM can now Suggest triage from the inbox — Claude reads the text body PLUS up to 3 attached photos via Anthropic's vision content blocks. The photos are forwarded as base64 image blocks alongside the text in the same /v1/messages call. HEIC/HEIF photos are stored but not sent to Claude (it only accepts JPEG/PNG/WEBP/GIF).

Branding

Today the report page shows the agency name (if an agency.logoUrl is set, it also renders the logo). No CSS customisation yet — the page is intentionally minimal so tenants submit instead of getting distracted.

Why not an app?

Three attempts at tenant apps elsewhere in the industry. None got >15% tenant adoption. Reasons:

  • Install friction. "Another app? For the one thing I do four times a year?"
  • Onboarding friction. Email confirmation, password, terms, consent — 100% dropoff on a burst-pipe report at 11pm.
  • Update friction. App store review delays, forced updates, version fragmentation.

A React Native tenant app is on the roadmap as an optional second channel, not a replacement. See Product Roadmap.

Rate limiting

Per-IP + per-token rate limits apply to the submit endpoint (5 submissions per token per hour). Trivially bypassable by a determined attacker, but good enough to block automated form-spam.

API

The submit endpoint accepts both multipart/form-data (preferred — for photos) and application/json (legacy/no-photos):

POST /api/v1/report/{token}
Content-Type: multipart/form-data; boundary=...

--...
Content-Disposition: form-data; name="rawText"

The dishwasher is making a loud rattling noise...
--...
Content-Disposition: form-data; name="reporterName"

Alex Nguyen
--...
Content-Disposition: form-data; name="reporterContact"

alex@example.com
--...
Content-Disposition: form-data; name="photos"; filename="leak.jpg"
Content-Type: image/jpeg

<binary>
--...--

Returns { "requestId": "...", "message": "We've received...", "photosAttached": 1 }.

Photos served back to the PM-facing UI through:

GET /api/v1/maintenance-requests/{id}/photos          → metadata array
GET /api/v1/maintenance-requests/{id}/photos/{photoId} → image bytes

Both require an authenticated PM session (RLS-bound).

See also