Contact Request Management

Summary

When a visitor sends an inquiry about a property, a ContactRequest record is created linked to both the property and its owning Pro. The Pro can then list all their incoming contact requests (paginated, with property context) and update the status of each request to track their follow-up workflow (new → read → replied).

State Diagram

stateDiagram-v2
    state "Nouvelle demande" as new_request
    state "Lu par le pro" as read
    state "Répondu" as replied

    [*] --> new_request: Visitor submits contact form
    new_request --> read: Pro marks as read
    read --> replied: Pro marks as replied
    new_request --> replied: Pro replies directly
    replied --> [*]

Steps

1. List Contact Requests (Actor: pro)

GET /pro/contact-requests. Returns all contact requests where proId = request.proId, ordered by createdAt DESC, id DESC. Includes the linked property’s id, slug, and title for display context in the Pro’s inbox UI.

Supports cursor-based pagination (compound cursor (createdAt, id) base64url-encoded) with a limit query parameter. Returns { data, cursor: { next }, hasMore }.

Triggers: Pro opens their inquiries inbox Outcome: Paginated list of contact requests with property info

2. Update Contact Request Status (Actor: pro)

PUT /pro/contact-requests/:id with body { status: string }.

Ownership check: prisma.contactRequest.findFirst({ where: { id, proId: request.proId } }). If not found (or belongs to another pro’s property) → 404 NOT_FOUND.

Valid statuses: new_request (maps to DB value “new”), read, replied. The status is cast directly to the Prisma enum — the schema or enum definition enforces valid values.

Returns the updated ContactRequest record.

Triggers: Pro reads or responds to an inquiry Outcome: Contact request status updated in DB

Error States

  • Contact request not found or owned by different pro → 404 NOT_FOUND
  • Unauthenticated → 401 (authenticatePro hook)
  • CNI gate active → 403 CNI_REQUIRED

Notes

  • Contact requests are created by the public-facing contact form (not in scope of this process file — that is an Acheteur/public-facing flow).
  • The ContactStatus enum in the DB uses "new" for what the application layer calls new_request.
  • There is no bulk status update endpoint — each request must be updated individually.