*"Alice signed up on Tuesday. Why isn't she in the onboarding flow?"*

Run counts tell you who **did** enrol and say nothing at all about the far larger
number of people who didn't. The **enrolment decision trace** is the other side of
that: every time a trigger signal reaches a workflow, SendOps records what the
enrolment path decided about that contact and **why** — whether the answer was
yes or no.


  The trace is kept for **14 days** and then dropped. It exists so a person can
  answer a question in a support conversation, not so you can build a report on
  it. If you need a lasting record of what happened to a contact, that is the
  [run timeline](/workflows/overview#monitoring) and the
  [audit log](/team/audit-log).


## Looking one up


  <Step title="Open the workflow">
    Go to the workflow in question and open **Enrolment decisions**.
  </Step>

  <Step title="Find the contact">
    Search by email address, or paste a contact id straight in — a support
    conversation usually carries one.
  </Step>

  <Step title="Read the decisions">
    The most recent decisions for that contact come back newest first: what fired,
    what was decided, and the reason in plain words.
  </Step>




You can also read the trace from a contact's own page, to see every workflow that
considered them, and over the API at
`GET /v1/workflows/{id}/enrollment-decisions` — optionally narrowed with
`contact_id` — which takes an email address or `external_id` as readily as a
contact id — and with a `since` timestamp that is clamped to the 14-day window
rather than failing.

Alongside the rows, a **tally by reason** shows the shape of the whole picture:
not just why Alice was skipped, but how many other contacts were skipped for the
same thing. That is often the more useful number — one person missing is a
question; four thousand missing for `entry_predicate_false` is an answer.


  A decision **row** records a contact id and never an email address, so nothing
  in the trace itself exposes who was skipped.

  Asking the question is separate. `contact_id` accepts an email address or your
  own `external_id` as well as a contact id, so you no longer have to resolve the
  person through the [Contacts
  API](https://developers.sendops.dev/api-reference/contacts) first. Because
  resolving an address *is* a contacts read, those two forms need the
  `api.contacts.view` scope alongside the workflow scope — a key with workflow
  access alone must pass a contact id. A contact who doesn't exist has no
  decisions, which is itself usually the answer.


## What a decision says

Each row carries:

- **What fired** — the trigger kind (a segment, an event, a custom activity, or a
  date relative to an attribute) and the segment key, event name or activity name
  it fired on.
- **The decision** — **enrolled**, **skipped**, or **errored**.
- **The reason**, on anything that isn't an enrolment.
- **Detail** that makes the reason actionable — the predicate text, the timestamps
  involved, the workflow's status at the time.
- **The run** it produced, when the answer was yes.
- **Whether the workflow was [shadowing](/workflows/shadow-mode)** at the time.
- **When** it happened.

**Errored is deliberately not the same as skipped.** A skip is the policy working
as written. An error is the enrolment path itself failing. From outside, a contact
missing for either reason looks identical — which is exactly why the two are
labelled differently here.

## The reasons a contact is skipped

| Reason | What it means |
|---|---|
| **Workflow not active** | The workflow was a draft, paused, archived or invalid. None of those enrol anybody. |
| **Trigger stale** | The signal named a trigger this workflow no longer declares, so there was nothing to enrol them into. |
| **Before the forward floor** | The event happened before this trigger started enrolling, so it counts as history rather than a new signal. |
| **Already ran, re-entry is once** | They have been through this workflow before, and it enrols each contact once. |
| **Success exit still matches** | Their last run finished on a named exit and they still match the trigger — re-enrolling would loop them through a journey they already completed. |
| **Entry predicate false** | They didn't match the trigger's `where` filter. |
| **Already in a live run** | They're part-way through this workflow right now. A contact holds one live run at a time. |
| **Outside the shadow cohort** | The workflow is rehearsing against a cohort List they aren't on. See [shadow mode](/workflows/shadow-mode). |
| **Definition wouldn't parse** | The workflow's source didn't compile, so the enrolment couldn't be evaluated at all. |
| **Recording the enrolment failed** | The write itself failed. This is an **errored** decision, not a skip. |

Most "why isn't Alice in the drip?" questions land on one of the first six. The
two that surprise people most often are **before the forward floor** — a workflow
only enrols on signals that arrive after its trigger started, unless you asked it
to [enroll existing contacts](/workflows/triggers#enrollment-scope) — and
**already in a live run**, where the contact is in the workflow, just not where
you were looking.

## What it doesn't tell you

The trace covers **enrolment** only: getting into the workflow. If a contact
enrolled and then didn't receive an email, the answer is further down — in the
run's timeline, in the consent re-check at the send step, or in the approval
queue. See [Sends, consent & approval](/workflows/sends-and-approval).

## What's next?

- [Triggers & enrollment](/workflows/triggers) — what the decisions above are deciding against.
- [Shadow mode](/workflows/shadow-mode) — rehearse a workflow before it sends.
- [Sends, consent & approval](/workflows/sends-and-approval) — why an enrolled contact might still not be mailed.