# Workflow & Automation Engine

Workflows automate actions that happen **outside of the real-time conversation** — follow-ups,
notifications, scheduled reminders, and custom actions triggered by time or contact state.

---

## Workflow Types

### 1. FOLLOW_UP
Sends an automated message to a contact after a period of inactivity or after a specific
property is set.

- **When to use:** "Contact hasn't replied in 24 hours → send a check-in message."
- **How it works:** Agent generates the message using the workflow's prompt + conversation context.
- **Key settings:**
  - `intervalMinutes` — how often to check/run (e.g., every 60 minutes)
  - `minMinutesSinceLastInbound` — wait N minutes after the contact's last message before triggering
  - `executionWindowStart/End` — only run between HH:MM and HH:MM (e.g., 09:00–21:00)
  - `onlyRunOnce` — if true, fires once per conversation and stops

### 2. CUSTOM_NOTIFICATION
Sends an alert to an external channel (not to the contact).

- **When to use:** "When a lead books a call → notify the team on Slack/WhatsApp/Email."
- **Outbound to the lead, not the team:** a `CUSTOM_NOTIFICATION` workflow pointing at a
  `WHATSAPP_TEMPLATE` destination with `destination: null` and `level: MESSAGE` instead delivers a
  Meta template **to the matching contact** — the automated outbound path. See
  [`outbound-whatsapp-playbook.md`](outbound-whatsapp-playbook.md) for the full destination →
  notification → workflow chain (and the manual/batch alternative).
- **WhatsApp alert to a fixed operator number:** on a `WHATSAPP_TEMPLATE` destination, set
  `destination` to a phone number (digits-only) and the template is delivered to **that fixed number**
  (e.g. the operator) instead of the lead — template variables still resolve from the lead (via the
  definition's token map). `null` = lead, a number = operator. For several numbers, link one destination
  per number to the same notification. **Note:** each template variable's `value` is a *token*
  (`firstName`, `phoneNumber`, `custom_<id>`, …), not literal text, and must resolve non-empty or Meta
  rejects the send with 131008. Details + the full token list in
  [`outbound-whatsapp-playbook.md`](outbound-whatsapp-playbook.md).
- **Destination channels:**
  - EMAIL
  - SMS
  - WHATSAPP (direct message)
  - WHATSAPP_TEMPLATE (pre-approved Meta template)
  - TELEGRAM
  - SLACK
  - SNS (AWS Simple Notification Service)
- **Notification levels:** INFO, WARNING, ERROR, SUCCESS, MESSAGE

### 3. CUSTOM_ACTION
Executes a custom logic block — used for integrations, CRM updates, or webhook calls.

- **When to use:** Trigger an N8n flow, call an external API, sync data to a CRM.

### 4. SCHEDULE_REMINDER
Sends a reminder relative to a scheduled appointment.

- **When to use:** "Send reminder 24 hours before a booked call."
- **Key setting:** `minutesBeforeScheduleTime` — how early to fire the reminder.

---

## Execution Rules

Workflows have a rich set of conditions that control **when** they run:

| Setting | Description |
|---------|-------------|
| `enabled` | Master on/off toggle (safe to UPDATE) |
| `intervalMinutes` | How often the engine checks this workflow |
| `minMinutesSinceLastInbound` | Minimum idle time after last contact message |
| `executionWindowStart/End` | Time-of-day window (HH:MM format) |
| `onlyRunOnce` | Prevent re-triggering after first run |
| `delayTriggerPropertyId` | Trigger delay based on when a custom property was set |
| `delayMinutesAfterPropertySet` | Minutes to wait after the property is set |
| `minutesBeforeScheduleTime` | For SCHEDULE_REMINDER: minutes before the appointment |
| `channels` | Only run for specific platforms (WhatsApp, Instagram, Facebook, etc.) |

---

## Property Conditions

Workflows can be gated by **custom property conditions** — the workflow only runs if the
contact's properties match all conditions.

| Operator | Description |
|----------|-------------|
| EQUALS | Property value equals X |
| NOT_EQUALS | Property value is not X |
| CONTAINS | String property contains X |
| NOT_CONTAINS | String property does not contain X |
| GREATER_THAN | Numeric property > X |
| LESS_THAN | Numeric property < X |
| IS_EMPTY | Property has no value |
| IS_NOT_EMPTY | Property has a value |
| IS_NOT_DEFINED | Property doesn't exist for this contact |

**Example:** Only run the follow-up if `call_booked = false` AND `lead_score > 5`.

---

## Workflow Runs

Every time a workflow fires, a `workflow_runs` row is created:

| Status | Meaning |
|--------|---------|
| PENDING | Queued, waiting to execute |
| IN_PROGRESS | Currently running |
| COMPLETED | Finished successfully |
| FAILED | Error during execution (see error log) |

To debug a failed workflow:
```sql
SELECT id, status, error, created_at
FROM workflow_runs
WHERE workflow_id = '<workflow-id>'
ORDER BY created_at DESC
LIMIT 20
```

---

## N8n Integration

Workflows can trigger external N8n automations via webhook:

| Trigger event | When it fires |
|--------------|--------------|
| MESSAGE_RECEIVED | On every inbound message |
| MESSAGE_SENT | On every outbound message |
| CONVERSATION_CREATED | When a new conversation starts |
| AGENT_RESPONSE | When the agent sends a response |

**Filtering options:**
- Agent-specific (only fire for selected agents)
- Channel-specific (only fire for WhatsApp, Instagram, etc.)

**Payload includes:**
- Conversation and message data
- Contact properties (optional)
- Conversation history (optional, configurable)

**Reliability:**
- Retry logic with configurable max attempts and timeout
- Stats: `lastTriggeredAt`, `totalTriggers`, `totalFailures`

---

## Managing Workflows Safely

**Toggle a workflow off:**
```sql
UPDATE workflows
SET enabled = false, updated_at = NOW()
WHERE id = '<workflow-id>'
RETURNING id, name, enabled
```

**List all workflows with status:**
```sql
SELECT id, name, type, enabled, interval_minutes, execution_window_start, execution_window_end
FROM workflows
ORDER BY type, name
```

**Never DELETE workflow rows.** Use `enabled = false` to deactivate.

---

## Common Patterns

### 24-hour follow-up
```
Type: FOLLOW_UP
intervalMinutes: 60
minMinutesSinceLastInbound: 1440  (24 hours)
executionWindow: 09:00–21:00
onlyRunOnce: false
Condition: call_booked = false
Prompt: "Generate a warm follow-up message to reignite the conversation"
```

### Booking confirmation notification
```
Type: CUSTOM_NOTIFICATION
Channel: SLACK
Condition: call_booked = true
Prompt: "Notify the team that {contact_name} booked a call on {schedule_date}"
```

### Pre-call reminder
```
Type: SCHEDULE_REMINDER
minutesBeforeScheduleTime: 1440  (24 hours before)
Channel: WHATSAPP
Prompt: "Send a reminder about tomorrow's call and ask for confirmation"
```
