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

# How it works

> How Dreamlit's database-driven architecture works: your app writes to the database, Dreamlit detects changes and executes email workflows automatically. No API calls or webhooks needed.

Dreamlit works by sitting on top of your [Supabase](/docs/configuration/data-sources/supabase) or [Postgres](/docs/configuration/data-sources/postgres) database, bringing AI to your data. It never modifies your data and won't slow down your app.

<CardGroup cols={3}>
  <Card title="1. Connect your app" icon="database">
    Our guided setup flow walks you through securely connecting your database and configuring your email sender.
  </Card>

  <Card title="2. Create and publish a workflow" icon="diagram-project">
    Use our AI workflow builder to setup your workflow from end-to-end, starting with a trigger.
    Publish the workflow to securely install it.
  </Card>

  <Card title="3. Sit back and relax" icon="couch">
    When the trigger condition is met, Dreamlit handles the rest. The workflow is executed in real-time,
    and notifications are delivered to your users.
  </Card>
</CardGroup>

<Info>
  **Database compatibility:** Dreamlit currently works with **Supabase** and **PostgreSQL** databases. We're actively working on support for additional databases.
</Info>

## What is database-driven email automation?

Database-driven email automation means using your existing database as the trigger source for notifications instead of adding API calls to your application code. When data changes in your database (a new user signs up, a payment succeeds, a trial expires), the notification system detects it and acts automatically.

* Your app doesn't need to "know" about notifications: it just writes to the database as usual
* The database is already the source of truth for your application state, so it eliminates synchronization issues
* This pattern has been used in enterprise systems for decades (CDC, outbox pattern, database triggers) but was previously too complex for small teams to set up

Dreamlit makes this approach accessible to any team with a Postgres database.

## How Dreamlit works with your app

```mermaid theme={null}
sequenceDiagram
    participant App as Your App
    participant DB as 🗄️ Your Database 🗄️
    participant Dreamlit as Dreamlit
    actor User as Your Users

    App->>DB: Persist events
    Dreamlit<<-->>DB: Listens for events
    Dreamlit->>User: Send notifications
```

<Steps>
  <Step title="App persists events">
    Your application simply writes to the database when something happens - a user signs up, a payment is processed, or any business event occurs. These events are likely already being persisted naturally as part of your app's normal operation. No need to worry about notification logic or add special code.
  </Step>

  <Step title="Database as source of truth">
    Your database remains the single source of truth.
  </Step>

  <Step title="Dreamlit monitors & executes">
    Dreamlit continuously monitors for new events and executes the appropriate workflows when trigger conditions are met, handling all the complexity of scheduling, retries, and delivery.
  </Step>
</Steps>

Experienced software devs will recognize that this is a classic event-driven architecture pattern, with the database as the source of truth.

<Note>
  **Using Supabase Auth?** Auth emails work slightly differently through API hooks. [Learn more about Supabase Auth configuration →](/docs/configuration/data-sources/supabase#supabase-auth-emails)
</Note>

## Why separate notifications from your app code?

### AI works better with focused scope

When you try to build everything in one place—app logic, UI, and notifications—you're asking AI to juggle too many concerns at once. This cognitive overload leads to mediocre results across the board.

By separating notifications into Dreamlit:

* **Your app's AI** (whether in Cursor, Lovable, Bolt, or Replit) can focus solely on user experience and business logic
* **Dreamlit's AI** specializes exclusively in notifications
* **Each AI operates at peak performance** in its domain, rather than struggling with competing priorities

### Notifications are a different beast

Notifications are fundamentally different from in-app interactions. They require:

* **Template design and previews** across email clients and devices
* **Delivery timing and scheduling** (wait steps, business hours, time zones)
* **Engagement tracking** (opens, clicks, unsubscribes)
* **Compliance handling** (CAN-SPAM, GDPR, unsubscribe management)

These concerns don't belong mixed with your app's user interface code. By keeping them separate, each system can excel at what it does best.

### Database as the perfect intermediary

Your database schema is inherently self-documenting—it tells both systems exactly what data exists and how it's structured. This makes it the ideal boundary between your app and notification system:

* **No API contracts to maintain** - The database schema IS the contract
* **Natural event sourcing** - Data changes are events by definition
* **Context is built-in** - Table relationships and column names provide the context AI needs, and when schemas are ambiguous, Dreamlit's AI proactively asks clarifying questions
* **Zero coupling** - Your app doesn't know Dreamlit exists, and vice versa

### Database-driven vs API-driven email: how they compare

| Concern            | Database-driven (Dreamlit)                                 | API-driven (Resend, SendGrid)                           |
| ------------------ | ---------------------------------------------------------- | ------------------------------------------------------- |
| **Coupling**       | Zero: app doesn't know about notifications                 | Tight: email-sending code scattered through app         |
| **Event source**   | Data changes ARE the events                                | You must explicitly fire events from code               |
| **Schema context** | AI reads your schema directly                              | You describe your data model manually                   |
| **Missed events**  | Database triggers capture everything, even during downtime | If your API call fails or is missing, the event is lost |
| **Deployment**     | Change notifications without redeploying your app          | Notification changes require code changes and deploys   |

### Battle-tested architecture pattern

This isn't a new pattern—it's how enterprise systems have been built for decades:

* **Event-driven architecture** using your existing database as the event store
* **Microservices boundary** without the complexity of service meshes or API gateways
* **Future-proof flexibility** - Swap out either system without touching the other

The result? Each tool does one thing exceptionally well, rather than multiple things poorly. Your notifications become as polished as your app, without compromising either.

## Architecture deep dive

When a workflow is published, Dreamlit adds a tiny database trigger on the relevant table that simply logs an event within the `event_log` table in
a separate `dreamlit` schema. From there, Dreamlit periodically efficiently polls this `event_log` table, and launches workflows when new events are detected.

This approach allows for a secure, efficient, and reliable way to listen for events and handle your workflows:

* Virtually no impact on database performance: database triggers are [highly efficient](https://infinitelambda.com/postgresql-triggers/) and non-blocking.
* Even in the unlikely event that Dreamlit is temporarily down ([our uptime is 99.9+%](https://status.dreamlit.ai/)), events will continue to accrue. When Dreamlit is back online, it will pick up where it left off and fire off any unprocessed events.
* After the event is picked up, Dreamlit handles each step in a robust, durable manner, automatically retrying any failed steps.

### The lifecycle of an event

```mermaid theme={null}
sequenceDiagram
    box Your Application
    participant App as Your Application
    participant DB as Your Database
    participant EventLog as dreamlit.event_log <br> in Your Database
    end
    participant Dreamlit as Dreamlit Engine
    actor User as Your End User

    Note over App: Some activity
    App->>DB: Insert/update row
    Note over DB: Row changed
    DB->>EventLog: Trigger executes
    Note over EventLog: Event recorded
    Dreamlit->>EventLog: Monitor changes
    Note over Dreamlit: Workflow triggered
    Dreamlit-->>User: Send notification
```

<Steps>
  <Step title="Some app activity occurs">
    Your database is your source of truth. When some key action occurs in your app, it's reflected as a change to a row in your database.
  </Step>

  <Step title="Trigger executes">
    When that row changes, a database trigger is executed that logs a row in the `dreamlit.event_log` table. It's fully managed by Dreamlit and built to be lightweight.
  </Step>

  <Step title="Dreamlit picks up event">
    Dreamlit tracks your `dreamlit.event_log` table for new events. When a new event is recorded in this table, Dreamlit detects it within seconds and idempotently kicks off any associated workflow.

    If you have a lot of events and you'd like to clean up event data, you can periodically truncate the table to prune old events.
  </Step>

  <Step title="Workflow executes">
    Each step in your workflow is run in a robust, durable manner. For any wait steps, we handle the scheduling for you. If any steps fail, we automatically retry with exponential back-off.

    For hard failures, we escalate to you. Any failed runs can be re-run manually in the Dreamlit dashboard.
  </Step>

  <Step title="Notifications are delivered">
    For any notification steps, templates are populated, and the final notification(s) are sent to the appropriate recipient or channel.

    If a workflow is sandboxed, then they're held for manual review and release in the Dreamlit dashboard.
  </Step>

  <Step title="Engagement events are captured">
    Dreamlit handles capturing analytics on the notifications sent, including delivery status and engagement metrics.
  </Step>
</Steps>

<Note>
  In addition to the `event_log` table, Dreamlit may create other small
  housekeeping tables in the `dreamlit` schema. It never writes to your existing
  tables (beyond installing triggers).
</Note>

### What is a database schema and why does Dreamlit need one?

A **schema** is a grouping of tables, functions, and other database objects. It's a way to organize your tables, often for security reasons. [Learn more about schemas](https://supabase.com/docs/guides/database/tables#schemas).

Dreamlit manages its objects in a dedicated `dreamlit` schema to provide a clean separation between your application and Dreamlit-managed objects. This allows events to be detected in realtime while ensuring events don't get lost. This separate schema approach is inspired by similar techniques used in tools like [Supabase](https://supabase.com) and [Hasura](https://hasura.io/).

## How do I make schema changes without breaking workflows?

Dreamlit workflows reference your table and column names. When you rename or remove columns that a workflow depends on, that workflow can break. Here are two approaches to keep things smooth.

### Option 1: Unpublish, change, then publish again

If you don't mind brief downtime for your workflows, this is the quickest approach.

1. **Unpublish** any workflows referencing the old column/table.
2. **Apply** your schema update.
3. **Adjust** your Dreamlit workflow steps to reference the new schema.
4. **Publish** your workflow again.

### Option 2: Phased migration

You can follow the ["expand, migrate, and contract pattern"](https://planetscale.com/blog/backward-compatible-databases-changes#the-expand-migrate-and-contract-pattern) to make your schema changes.

1. **Add** new columns or tables.
2. **Write** to both old and new columns from your application.
3. **Backfill** data into the new columns or tables.
4. **Update** your Dreamlit workflow to reference the new columns/tables, and possibly update your application to reference the new columns/tables.
5. **Remove** (or rename) the old columns/tables once no longer needed.

This approach avoids downtime for dependent workflows.

## References

* [Supabase Auth configuration](https://supabase.com/docs/guides/auth/auth-smtp)
* [PostgreSQL trigger performance overview](https://infinitelambda.com/postgresql-triggers/)
* [Supabase database schemas](https://supabase.com/docs/guides/database/tables#schemas)
* [Expand, migrate, and contract pattern](https://planetscale.com/blog/backward-compatible-databases-changes#the-expand-migrate-and-contract-pattern)

***

Last validated: 2026-02-26
