One queue, one record
Every message becomes one job in one store. The same sender, subject and text twice is one job, not two.
Start a project →
Live on this site · every contact-form message goes through it
When you send us a message, it lands in a queue. Fixed rules label it, a fixed set of rules decides what may happen next, and a routine enquiry gets an acknowledgement while anything sensitive waits for a person. Each message gets one reply at most, and every step is logged.
How it is built
Every message becomes one job in one store. The same sender, subject and text twice is one job, not two.
A worker claims a job for a few minutes. If it stops halfway, the lease runs out and the job is picked up again, with a limit on attempts.
The model is not configured on this install, so fixed rules produce the same three labels — category, urgency, and whether a person should read it. Either way nothing writes the reply.
A reply is written to an outbox before it is sent, and the provider's answer is recorded. One reply per job, never two.
What happens to your message
The contact form checks the message and queues it. The team is emailed a copy at the same moment, so nothing depends on the queue alone.
Every message becomes one job. A job in progress is claimed, so two workers cannot both handle it, and the queue stops taking work at 500 open jobs.
Fixed rules drop bulk mail and anything from a no-reply, bounce or internal address, and flag urgent words and support problems.
Bulk mail and robot mailboxes stop here. Our own domains and no-reply, bounce and notification addresses are refused before anything is sent.
The model reads the message as data and returns a category, an urgency from 0 to 5 and a "needs a person" flag. If the call fails, the rules decide alone.
The AI's labels can only make the outcome more careful. Complaints, support problems and urgent messages always wait for a person.
A routine enquiry gets a short, fixed acknowledgement. The system does not write its own replies; only your first name is used.
Everything that is not routine sits on a review board until someone approves a reply or closes it.
The worker sends from the outbox. A failed send is retried after 1, 2, then 4 minutes and so on, and set aside for a person if it keeps failing.
The result of every send is recorded. Finished messages are deleted after a set period; the details are in the privacy notice.
Choose a path above to see which steps it takes, or select any step to see what happens there. You can drag the steps around; Reset puts them back.
State model
| Status | Meaning | Can move to |
|---|---|---|
| new | Queued, not yet claimed | processing |
| processing | A worker holds the lease and is classifying it | outbox, awaiting review, ignored, dead letter |
| awaiting review | Needs a person before any reply | outbox, ignored |
| outbox | A reply is staged, or retrying after a failed send | sent, dead letter |
| sent | The email provider accepted the reply | closed |
| ignored | Bulk mail, a no-reply sender, or closed by a person | closed |
| dead letter | Kept failing, or the address was refused | a person decides |
Guardrails
The pipeline
Plain PHP and locked JSON files, a worker that runs every minute, and a review board behind a password. The same design works with Gmail, Outlook or a helpdesk as the intake.
Current status: replies are being written to a log file on our server rather than emailed — the delivery provider is not configured yet, so nothing has left the building. Enquiries are still stored and read by a person.
Contact form
└─ validate + dedupe → job (new)
└─ worker lease → processing
├─ rules: spam / no-reply / urgent / support
├─ Claude: category · urgency · needs_human
├─ policy gate → acknowledge | review | ignore
├─ outbox (one reply per job)
├─ send → sent | retry with backoff
└─ dead letter → a person decides