Skip to content

Messages Module

TLDR: Bidirectional messaging between couples and vendors with AI-powered response detection, automated reminders at 24h/72h/168h intervals, and event-driven cancellation when recipients respond.

Overview

The Messages module handles all communication between couples and vendors in the Wedissimo marketplace. It provides threaded conversations, read receipts, AI-powered response detection, and automated reminder scheduling to ensure timely vendor responses.

Primary use cases:

  • Couples inquiring about vendor services
  • Vendors responding to booking requests
  • Follow-up reminders when messages go unanswered
  • Conversation history and audit trail

Architecture

Core Models

Message

  • Primary communication entity within a thread
  • References sender via sender_id (User)
  • UUID primary keys, soft deletes enabled
  • Scanned for disintermediation attempts

MessageThread

  • Groups messages between couple and vendor
  • Links to vendor_id and user_id (the couple)
  • Tracks response_needed_at, last_response_at

MessageThreadReminder

  • Pre-scheduled follow-up notifications
  • Bidirectional: vendor OR couple can be reminded
  • AI-powered needs-response detection via Vertex AI
  • Intervals: 24h, 72h, 168h (levels 1, 2, 3)
  • Event-driven cancellation when recipient responds

Database Design

Message Threads Table (message_threads)

- id (uuid, primary key)
- vendor_id, user_id (foreign keys)
- subject (nullable)
- response_needed_at, last_response_at (timestamps)
- awaiting_response_to_message_id (FK)

Messages Table (messages)

- id (uuid, primary key)
- thread_id (uuid, FK to message_threads)
- sender_id (uuid, FK to users)
- body (text), body_redacted (nullable)
- status (pending, approved, flagged, blocked, redacted)
- created_at, updated_at, deleted_at

Message Thread Reminders Table (message_thread_reminders)

- id (uuid, primary key)
- thread_id, trigger_message_id, recipient_id (FKs)
- recipient_type ('vendor' or 'couple')
- level (1, 2, or 3)
- scheduled_for (timestamp)
- sent_at, cancelled_at (nullable timestamps)
- cancellation_reason (nullable)

Key Indexes:

  • reminders_due_idx: Partial index WHERE sent_at IS NULL AND cancelled_at IS NULL
  • reminders_cancel_idx: Compound on (thread_id, recipient_id, sent_at, cancelled_at)

Key Relationships

MessageThread
├── belongsTo: vendor (Vendor)
├── belongsTo: user (User - the couple)
├── hasMany: messages (Message)
└── hasMany: reminders (MessageThreadReminder)

Message
├── belongsTo: thread (MessageThread)
├── belongsTo: sender (User)
└── hasMany: reminders (MessageThreadReminder)

MessageThreadReminder
├── belongsTo: thread (MessageThread)
├── belongsTo: triggerMessage (Message)
└── belongsTo: recipient (User)

Message Reminders System (FOU-59)

Bidirectional Reminder Logic

Vendor Reminder (vendor_no_response):

  • Triggered when couple sends message to vendor
  • Vendor hasn't responded within threshold
  • Reminds vendor to respond to inquiry

Couple Reminder (couple_no_response):

  • Triggered when vendor sends message to couple
  • Couple hasn't responded within threshold
  • Reminds couple to respond to vendor's answer

AI-Powered Response Detection

The system uses Google Vertex AI to determine if a message requires a response:

Why AI detection?

  • Simple heuristics fail ("Thanks!" still needs vendor reply with details)
  • Question marks don't always indicate questions
  • Context matters: "Great, I'll book!" vs "Can you do Saturday?"

How it works:

  1. Message created event dispatched
  2. AI analyzes message content and conversation context
  3. If response needed: schedule reminders at 24h, 72h, 168h
  4. If no response needed: no reminders scheduled

Example prompts:

  • "What's your pricing for 100 guests?" → Needs response
  • "Thanks for the info, we'll be in touch!" → No response needed
  • "Great! Can you also do photography?" → Needs response

Automated Scheduling

When AI detects a message needing response:

  1. 24-hour reminder: First gentle nudge
  2. 72-hour reminder: Second follow-up if still no response
  3. 168-hour reminder: Final reminder after 1 week

Event-driven cancellation:

  • Recipient responds → Cancel all pending reminders for that message
  • Sender deletes message → Cancel all reminders
  • Thread closed → Cancel all thread reminders

Implementation Notes

Queue processing:

  • AI check: CheckMessageNeedsResponseJob - analyzes message, schedules reminders
  • Reminder delivery: messages:send-reminders command (every 5 min) → SendSingleReminderJob
  • Reminder cancellation: ParticipantRespondedToThread event → CancelRemindersOnResponse listener

Notification channels:

  • Email (primary) via UnrespondedMessageReminderNotification
  • Escalating tone: friendly (24h) → urgent (72h) → final (168h)

Key Concepts

Thread Context

Messages are grouped into threads based on:

  • Listing context: Inquiry about specific vendor listing
  • Package context: Questions about specific package/service
  • General inquiry: Direct vendor contact without listing context

Thread context enables:

  • Conversation history
  • Related message grouping
  • Context-aware AI analysis

Read Receipts

Read state tracking:

  • read_at timestamp on Message model
  • Updated when recipient views message
  • Enables "seen" indicators in UI

Privacy considerations:

  • Read receipts visible to sender
  • Configurable per user (future: opt-out)

Soft Deletes

Messages use soft deletes to:

  • Maintain conversation integrity
  • Preserve audit trail
  • Enable "undo" functionality
  • Comply with data retention policies

Deletion behavior:

  • Sender can delete their sent messages (soft delete)
  • Recipient can delete from their inbox (soft delete)
  • Hard deletes: admin only, legal compliance

Service Layer

MessageService

  • Core business logic for message creation
  • Dispatches CheckMessageNeedsResponseJob after creation
  • Fires ParticipantRespondedToThread event

MessageReminderService

  • getDueReminders() - queries pending reminders (uses partial index)
  • sendReminder() - sends with pessimistic locking
  • cancelPendingRemindersForRecipient() - cancels on response

VertexAiScanningService

  • scanForNeedsResponse() - heuristics + AI fallback
  • scanForDisintermediation() - contact info detection
  • scanForAvailability() - vendor availability detection

Events & Listeners

ParticipantRespondedToThread

  • Fired when any participant sends a message
  • Contains: thread, sender (User), senderType ('vendor'/'couple')
  • Listened by: CancelRemindersOnResponse

CancelRemindersOnResponse

  • Cancels all pending reminders where sender was the intended recipient
  • Updates thread last_response_at

Authorization

Policy-based authorization:

  • Users can only message vendors they have access to
  • Vendors can only view messages for their listings
  • Thread participants can view all thread messages
  • Admins have full access for moderation

Permission checks:

  • MessagePolicy::create(): Can user send message to recipient?
  • MessagePolicy::view(): Can user view this message?
  • MessagePolicy::delete(): Can user delete this message?

Integration Points

Vendor Module

  • Messages tied to vendor listings
  • Vendor response metrics (future: response time tracking)

User Module

  • Polymorphic sender/recipient relationships
  • User notification preferences

Notification Module

  • Email delivery for message notifications
  • In-app notification badges
  • SMS delivery (optional)

AI Services

  • Vertex AI for response detection
  • Future: sentiment analysis, auto-categorization

What's NOT in This Documentation

API endpoints: See OpenAPI specification in modules/Messages/OpenAPI/

Request/response formats: See OpenAPI schema definitions

Authentication flows: See Authentication module docs

Frontend integration: See UI component documentation

Next Steps

For developers:

For API consumers:

  • Review OpenAPI documentation for endpoint details
  • Check authentication requirements in Auth module docs
  • See rate limiting policies in API documentation

Wedissimo API Documentation