# Enrichment

Every completed submission on a claimed site (and every test response, so you can see the result) is enriched before it is delivered, so the dashboard, emails, Slack messages, webhooks and database rows all carry the same signals.

```json
"enrichments": {
  "email": { "address": "ada@acme.com", "domain": "acme.com", "isFreeMail": false, "isRole": false, "isDisposable": false, "mxValid": true },
  "duplicate": { "count": 1, "firstSeenAt": "2026-09-30T10:12:00Z" },
  "velocity": { "ipSubmissions1h": 1 },
  "company": { "domain": "acme.com", "name": null, "website": "https://acme.com" }
}
```

## Signals

| Signal | Meaning |
| --- | --- |
| `email.isFreeMail` | Gmail, Outlook, iCloud and similar consumer domains |
| `email.isRole` | `info@`, `sales@`, `support@` and similar shared addresses |
| `email.isDisposable` | Throwaway domains |
| `email.mxValid` | The domain accepts mail (`null` when the lookup timed out) |
| `duplicate` | The same email submitted this form before |
| `velocity` | Submissions from the same IP across the project in the last hour |
| `company` | Derived from a work-email domain; the name is filled when a company data provider is configured (follow-up) |

The dashboard shows these in plain words under a response's What Kibble found ("Work address at acme.com", "Responded once on this form"), with the raw values under Technical details. There is no score: for a judgement of how well a response matches what you are looking for, use [triage](/docs/ai-review.md), whose fit level is based on your own description. The signals are also part of what triage is told.

## What enrichment never does

- It never blocks or drops a submission. Signals are context, not a verdict.
- It never changes the visitor's values. Enrichment lives in its own `enrichments` object.
- It never runs on partials, spam or sandbox projects.
