> ## Documentation Index
> Fetch the complete documentation index at: https://docs.buildbetter.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Audience & Delivery

> Target respondents with signal filters, deliver by email or in-app, and understand throttles, suppression, and recipient states

## Signal-Led Targeting

Audience selection reuses the **Signals v2 filter builder** — the same signal fields, feedback metadata, custom person and company properties, nested rules, and generated goal-to-filter path you use on the Signals page. There is no survey-only targeting language to learn.

The flow:

<Steps>
  <Step title="Describe who you want">
    State the goal next to the action that generates filters. BuildBetter turns it into real, editable Signals v2 filters — including nested rules — rather than hidden filter state or a count-only badge. You can inspect and adjust every rule before running it.
  </Step>

  <Step title="Review matched people">
    People are the primary result. Matching signals appear as *why-matched evidence* beneath each person, so you can audit inclusion. Each person is inspectable through the standard profile and company components.
  </Step>

  <Step title="Approve before adding">
    Nobody becomes a recipient until you approve them. Matched signal rows are deduped by canonical person email first, so one person with five matching signals is one row, not five.
  </Step>
</Steps>

Recipient metadata records the person's canonical id, matched signal count, matched signal ids, and the latest signal summary where available — so months later you can still answer "why was this person surveyed?"

<Tip>
  Manual email entry is available for one-off recipients. It's a supplement, not the main model — workspace-contact search is deliberately not the primary targeting path.
</Tip>

## Delivery Channels

A survey's channel determines how it reaches people:

| Channel     | Delivery                                                   | Notes                                                |
| ----------- | ---------------------------------------------------------- | ---------------------------------------------------- |
| `email`     | Invitation email with a tokenized respondent link          | Queues on activation                                 |
| `intercept` | Rendered inside your installed BuildBetter feedback widget | No email sent                                        |
| `both`      | Either path can be used                                    | Email *and* in-app both remain open until completion |

For `both` surveys, email delivery problems — `failed`, `suppressed`, `billing_blocked` — do **not** close the in-app path. Only completion, or the survey/intercept activation rules, close it.

### In-app delivery

Widget delivery requires your app to identify the current user by passing `person.email` and `person.identitySignature` through `BuildBetterFeedback.create(...)` or the equivalent script data attributes.

`identitySignature` is a hex HMAC-SHA256 of the normalized lowercase email, using the widget config's `identityVerificationSecret`. The public widget config endpoint strips that secret, so it is never exposed client-side.

When the widget opens, it asks for an active survey recipient matching that verified email. If one exists, the widget renders the survey questions in place of the normal feedback form.

<Warning>
  Without a valid `identitySignature`, the widget cannot match a recipient and will show the normal feedback form instead of your survey. This is the most common reason an in-app survey appears not to fire.
</Warning>

## Recipient States

The recipients table distinguishes these states, and they are worth reading literally:

| State             | Meaning                                                                      |
| ----------------- | ---------------------------------------------------------------------------- |
| `pending_send`    | Queued, not yet dispatched — includes waiting on throttle or credit capacity |
| `sending`         | Claimed by the worker, delivery in progress                                  |
| `sent`            | Accepted by the email provider                                               |
| `opened`          | Respondent session loaded                                                    |
| `responded`       | Response completed                                                           |
| `failed`          | Provider rejected the send; retryable, with error detail                     |
| `suppressed`      | Bounced, complained, or unsubscribed — will not be sent again                |
| `billing_blocked` | Credits exhausted before send; retried automatically once capacity returns   |

For `intercept` recipients, the first widget display marks them `sent`. For `both` recipients, they stay `pending_send` so the queued email still goes out.

## Send Limits

Throttles apply **even when you have credits available**, to protect sender reputation:

* **1,000 invitations per hour** per organization
* **500 invitations per hour** per survey

Recipients beyond current capacity stay `pending_send`. A once-per-minute cron asks the survey worker to drain eligible `pending_send` and `billing_blocked` recipients through the same bounded path as capacity frees up — you do not need to retry manually.

Queue publication uses an atomic PostgreSQL reservation at both organization and survey scope. The organization lock also serializes the billing allowance check and counts pending reservations that haven't hit the usage ledger yet, so concurrent bulk imports cannot over-reserve invitation credits.

## Sender Identity and Deliverability

Invitations send from **`BuildBetter Surveys <noreply@surveys.buildbetter.ai>`**, or your configured survey display name at that verified domain. Reply-to is configurable; the From domain is not — arbitrary unverified customer domains are deliberately not supported.

Respondent links use the environment's verified survey domain:

| Environment | Survey link domain                       |
| ----------- | ---------------------------------------- |
| Production  | `https://surveys.buildbetter.ai`         |
| Staging     | `https://surveys-staging.buildbetter.ai` |
| Testing     | `https://surveys-testing.buildbetter.ai` |

<Note>
  Custom DKIM and customer sender domains are follow-up scope, not part of the current release.
</Note>

### Token security

Recipient rows store only **invite token hashes**, never raw tokens. Raw tokens exist only while an invitation is being queued or sent. Tokens do not encode recipient email or metadata.

When a response link is rotated, prior hashes stay valid only for one-click unsubscribe — not for opening a new session.

### Suppression

Bounces, spam complaints, and unsubscribes are recorded as **organization-wide** suppressions. A complaint on one survey suppresses future sends to that address across every survey in the organization.

* **Unsubscribe** — invitations carry RFC 8058 one-click unsubscribe headers. Unsubscribing records the suppression and marks an incomplete recipient suppressed, including when the link came from an earlier invitation.
* **Bounce and complaint** — Resend sends signed events, verified against the exact raw body with `APP_RESEND_WEBHOOK_SECRET` before anything is recorded.

## Test Sends

Delivery setup can send an out-of-band test email. It uses the same renderer and organization sender settings, but it does **not** create recipients, enqueue jobs, or affect delivery counts — and preview sends are never billable.

Preview emails link to a fillable saved survey using survey-specific sender identity, so what you check is what respondents get.

## Next

<CardGroup cols={2}>
  <Card title="Responses & Signals" icon="chart-line" href="./responses-and-signals">
    What happens after someone answers.
  </Card>

  <Card title="Settings & Billing" icon="gear" href="./settings-and-billing">
    Credit rates, allowance checks, and organization settings.
  </Card>
</CardGroup>
