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

# Lookups

> Read your own Postgres before a send, to personalize copy or decide whether to send at all.

Events say what a user did. Your database knows the rest: their plan, their
unfinished order, the name of the person who just messaged them. A **lookup**
is an approved SQL query that runs against your database right before a flow
sends, for that one user.

Use it two ways:

* **Fill in copy**: `{{ lookup.cart.item_name }}` in a message
* **Guard a send**: only send if `cart.item_count > 0`

## Connect your database

On the **Database** page, connect your Postgres. We only ever read it, and we
enforce that on our side whatever your grants say.

The easiest path is to let us create the reader role for you. Paste a URL for
a role that can create roles, tick the tables you want to share, and press
Run. We create a `pushnotifai_reader` role that can read exactly those tables
and nothing else. The page shows the exact SQL first. That admin URL is used
once and never stored.

Or create a read-only role yourself and paste its URL. We refuse to store a
URL for a superuser or for any role that can write or create.

> [!NOTE]
>
> Your database must be reachable from the internet over TLS. Private and
> internal addresses are refused.

## Write a lookup

A lookup is one `SELECT`. The only parameter is `:user_id`, which is the id
you passed to [`identify`](https://docs.pushnotif.ai/sdk/identifying-users):

```sql theme={null}
select item_name, count(*)::int as item_count
from carts
where user_id = :user_id and status = 'open'
group by item_name
limit 1
```

Every version of a lookup is saved and never changes. A version must be
**approved** by a person before any flow can use it live. The Composer can
draft lookups too, but approving its proposal never approves its SQL.

## Use it in a flow

A flow names the exact lookup version it runs, with optional guards:

```json theme={null}
"lookups": [
  { "key": "cart", "version": 3,
    "where": [{ "property": "item_count", "op": "gt", "value": 0 }] }
]
```

## When a lookup fails

**Nothing sends without its lookup row.** If the query errors, times out,
returns no row, returns the wrong type, or a guard says no, the send is skipped
and the reason is recorded. Every run is logged, and each send in the dashboard
shows the row it read.

If quiet hours delay a send, its lookups run again when it's released, so the
copy uses current data.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.