Systems EngineeringOwn project, live demo

Wedding Matching App

A two-sided wedding services marketplace in Laravel, with timed matching rounds, AI-ranked providers and a locked credits ledger

By Lotfi Otmanine

Client
Own project, public demo with fictional data
Engagement
Portfolio build
Stack
PHP 8.2+Laravel 12Livewire 4Tailwind CSSViteMySQL / MariaDBRedisLaravel SanctumSpatie Permissionn8nClaude APIWhatsApp Cloud APIDockerCoolify

Key result

1 winner

row-locked lead acceptance

01

Context

A couple submits one wedding request, and the app finds local providers for each service through timed matching rounds. Providers pay one credit to unlock the couple's contact details. The app runs in English and French, with prices in EUR for France and USD for the USA.

It is a public demo at wedding-app.oldev.site. The cities, businesses, couples and phone numbers are fictional, the demo checkout takes no real payment, and the demo data is restored every day.

02

The Challenge

  • One request covers many services, and each service needs its own match, its own clock and its own outcome.
  • Several providers can accept the same lead at once: only one may win, and only one credit may be spent.
  • Rounds must close on time even when the automation layer is down.
  • Events sent to n8n can fail in transit, so they must be retried, logged and replayable.
  • Two languages and two currencies across the public site, the provider area and the back office.

03

Scope and Approach

One request, many matches

A wedding request is split into one service request per service, such as photo, catering or venue, and each one is matched on its own.

Ranking

Laravel exposes the eligible providers through a versioned Sanctum API, and an n8n workflow asks the Claude API to rank them.

Timed rounds

The top providers, 3 by default, are notified on WhatsApp through the WhatsApp Cloud API and have 120 minutes to respond. If nobody accepts, the next round starts with different providers. After 3 rounds, the request opens to every eligible provider on the marketplace.

Credits and unlock

The first provider to accept pays one credit and gets the couple's contact details. Every credit movement is written to a ledger with the resulting balance.

Provider area and back office

Livewire 4 screens for providers and administrators, roles through Spatie Permission, and a language switch that covers the public site, the provider area and the back office.

Delivery

A Docker image deployed with Coolify, with MySQL or MariaDB and Redis for cache and sessions in production.

04

Architecture flow

Couple submits a request
One service request per service
n8n and Claude rank providers
eligible providers from Laravel
Round: WhatsApp offers
120 minutes to respond
first accept
Contact unlocked
1 credit
timeout
Next round
up to 3
Open marketplace
every eligible provider

05

Technical Deep Dive

A round state machine

Each service request moves through new, three rounds with a matching state between them, and the open marketplace, ending in accepted, expired or deleted. A scheduled command closes timed-out rounds every 5 minutes, independently of n8n.

A credits ledger inside transactions

The credits service locks the provider's row inside a database transaction and writes each movement to the ledger with the resulting balance. Accepting a lead locks the service request too, so only one provider can win it.

Signed webhooks with queued retries

Domain events reach n8n through queued jobs, signed with HMAC-SHA256. A delivery gets up to 6 tries with exponential backoff, is logged, and can be replayed from the admin area or the command line.

Structured logging

Application events are written as JSON to a dedicated channel, with common personal fields redacted.

06

Engineering Decisions

The app keeps the clock

Rounds are closed by a scheduled Laravel command, not by the automation layer, so a stalled workflow cannot leave a request waiting forever.

AI ranks, the code decides

Claude only orders the providers Laravel has already found eligible. Rounds, credits and unlocks stay in tested application code.

A narrow API for n8n

n8n reads candidates and drives rounds through a versioned API protected by a token ability and rate limits, rather than through database access.

07

Results

  • A public demo in English and French, with fictional data restored every day.
  • 300 PHPUnit tests covering matching, credits, webhooks, the API, the admin area and demo mode.
  • One winner per lead, enforced by row locks inside database transactions.
  • Rounds close on schedule even when the automation layer is unavailable.
  • Every webhook delivery is signed, retried with backoff, logged and replayable.

Have a site that needs this kind of care?

Tell me what is breaking or what you need built. I will reply with how I would approach it and what I would check first.

Custom quote within 24 hours

Chat on WhatsApp