Contact Request Submission

Summary

Any visitor (authenticated or not) can send an inquiry about a specific property to the listing Pro. The form includes a hidden honeypot field to silently discard bot submissions. Rate limiting prevents spam at the infrastructure level. A ContactRequest record is created linking the sender’s details, the property, and the property’s Pro. The Pro sees incoming requests in their contacts dashboard.

State Diagram

stateDiagram-v2
    state "Formulaire soumis" as FormSubmitted
    state "Anti-spam déclenché" as HoneypotTripped
    state "Bien introuvable" as PropertyNotFound
    state "Demande enregistrée" as RequestCreated
    [*] --> FormSubmitted: visitor submits contact form
    FormSubmitted --> HoneypotTripped: website field has a value (bot)
    HoneypotTripped --> [*]: 201 success (silent discard, bot not alerted)
    FormSubmitted --> PropertyNotFound: propertyId does not exist
    PropertyNotFound --> [*]: 404 NOT_FOUND
    FormSubmitted --> RequestCreated: property found, honeypot empty
    RequestCreated --> [*]: 201 ContactRequest record

Steps

1. Submit Contact Form (Actor: acheteur)

Client POSTs to POST /contact-requests with:

  • propertyId — UUID of the target property
  • senderName — requester’s full name
  • senderEmail — requester’s email address
  • senderPhone — requester’s phone number (optional)
  • message — free-text inquiry
  • website — honeypot field (hidden via CSS in the UI, never shown to the user)

Rate limit: 5 requests per minute per IP.

Validation is handled by createContactSchema (JSON Schema).

Outcome: Request reaches the handler.

2. Honeypot Check (Actor: system)

If the website field contains any value, the submission is silently accepted with a 201 success response. No database record is created. The bot receives no indication that the submission was discarded, preventing adaptive behavior.

Outcome: Bot submissions are dropped invisibly; legitimate submissions continue.

3. Property Lookup (Actor: system)

prisma.property.findUnique({ where: { id: propertyId } }). If not found → 404 NOT_FOUND. The proId is extracted from the property to link the contact request to the correct Pro.

Outcome: Property and its proId confirmed.

4. Create Contact Request (Actor: system)

prisma.contactRequest.create() stores: propertyId, proId (from the property), senderName, senderEmail, senderPhone, message. Initial status is new_request (maps to DB value "new").

Outcome: 201 response with the created ContactRequest record.

5. Pro Reviews Request (Actor: pro)

The Pro accesses their contacts dashboard (authenticated, separate route: GET /pro/contacts). Contact requests are displayed with status new_request, read, or replied. The Pro can update status and respond directly to the sender via their own email client.

Outcome: Lead received and managed by the Pro.

Error States

  • Honeypot field filled → 201 silent discard (intentional, not an error state for the client)
  • Property not found → 404 NOT_FOUND — “Property not found”
  • Rate limit exceeded → 429 (handled by Fastify rate-limit plugin)