You want to ask a customer one question — *what is stopping you from upgrading?* — and act on the answer. This recipe does it with an email, one web page and one Tag Manager tag. Nobody fills in a form: the answer is the link they click.

The pieces:

- An **email** with one link per answer, each pointing at the same page with a different `?r=` value.
- One **answer page** on your website that says thank you. It needs no code.
- A Tag Manager **Track event** tag on that page that records `site_survey_answered` with `reason` set from the URL.
- A **promoted** `reason` property, so a rule can read it.
- A **workflow** that sends the email and branches on the answer.

It assumes Website Identity is already working: your backend sets the `so_vid` cookie and a **Page visit** tag is live. If not, start with [Installing the snippet](/website/installing-the-snippet).

## Build it


  <Step title="Write the email">
    Make a [template](/templates/template-management) with one link per answer, all to the same page:

    ```html
    <p>What is stopping you from upgrading?</p>
    <p><a href="https://www.example.com/survey/thanks?r=price">The price</a></p>
    <p><a href="https://www.example.com/survey/thanks?r=feature">A missing feature</a></p>
    <p><a href="https://www.example.com/survey/thanks?r=timing">Not the right time</a></p>
    <p><a href="https://www.example.com/survey/thanks?r=other">Something else</a></p>
    ```

    The page must be on a host under the domain your `so_vid` cookie is set for, so the snippet can read it there.
  </Step>

  <Step title="Publish the answer page">
    `/survey/thanks` is an ordinary page that thanks them. It does not read the parameter and needs no SendOps code — Tag Manager does the rest.
  </Step>

  <Step title="Add a Tag Manager variable for the answer">
    **Variables → User-Defined Variables → New**, type **URL**, **Component Type** = **Query**, **Query Key** = `r`. Name it `URL query r`.
  </Step>

  <Step title="Add the Track event tag">
    **Triggers → New**, type **Page View**, fire on **Some Page Views** where **Page Path** *equals* `/survey/thanks`.

    **Tags → New**, **SendOps Website Beacon**, **Tag type** = **Track event**:

    - **Event name**: `site_survey_answered`
    - **Event properties**: one row, name `reason`, value `{{URL query r}}`

    Attach the trigger and publish the container. A visit with no `r` in the URL still records the event, just without `reason`.
  </Step>

  <Step title="Promote the reason property">
    Go to **Audience → Activity properties** and click **Promote property**:

    - **Activity name**: `site_survey_answered`
    - **Property name**: `reason`
    - **Type**: `enum`, with the **Values** `price`, `feature`, `timing`, `other`

    Use exactly the values your links send. Once `reason` is an enum, a rule comparing it to a value that is not on the list — a typo like `"prise"` — is refused when you save it.

    The page properties (`path`, `title` and the rest) need no promoting; they are [built in](/audience/activities#built-in-properties-on-website-events). You can also promote through the API with `POST /v1/activity-properties`.
  </Step>

  <Step title="Check it works">
    Log into your product in a browser, then open one of the answer links in that browser. On **Workspace → Website**, `site_survey_answered` appears under **Events received** on the [health card](/website/troubleshooting-beacons#events-received), and your contact's timeline shows the activity with its `reason`.
  </Step>


## The workflow

Ask free-plan customers who keep coming back, wait for an answer, then act on it:

```sendflow
workflow "Why not upgrade" v1 {
  enter on activity.site_visit
    where attr.plan = "free"
      and count(activity.site_visit within 30d) >= 3
      and not exists(activity.site_survey_answered within 90d)
  reentry on rematch

  send "upgrade-survey"
  wait up to 7d until exists(activity.site_survey_answered within 7d) {
    timeout:
      exit
  }

  if exists(activity.site_survey_answered where reason = "price" within 14d) {
    send "pricing-options"
  } else if exists(activity.site_survey_answered where reason = "feature" within 14d) {
    add to list "feature-waitlist"
  } else {
    set attr.upgrade_blocker = "other"
  }
}
```

- `not exists(activity.site_survey_answered within 90d)` in the trigger stops anyone who has answered from being asked again for 90 days.
- The `wait … until` moves on as soon as an answer arrives; with no answer in seven days, the run ends.
- Each branch tests `reason` inside `exists(...)`, because a workflow condition is a rule about the contact, not about one event.

See [Triggers & enrollment](/workflows/triggers) for `reentry` and the [flow reference](/workflows/flow-reference) for `wait` and `if`.

## Things to know


  The answer is recorded only when the browser that opens the link carries the `so_vid` cookie — that is, one your customer has logged into your product with. Someone who reads the email on a phone they have never logged in on lands on the thank-you page, and nothing is recorded. If every answer must count, link to your own backend instead and record the answer with `POST /v1/activities`.


- **Clicking twice records twice.** Every load of the answer page is a new event. `exists(...)` does not care, but a `count(...)` would.
- **A changed mind counts both answers.** Someone who clicks *price* and then *feature* matches both branches' rules; the first matching branch wins. Put the answer you most want to act on first.
- **Values arrive as text.** The Track event tag sends every property as a string, which is why `reason` is promoted as an `enum` rather than a number.