# Version 3.0 SQLite Event Ledger Backend

This version rebuilds the SMS backend around a durable SQLite event ledger.

## What changed

### 1. SMS events are stored in SQLite

New table:

- `sms_events`

Every inbound webhook or polling result gets an event key. If the same webhook/polling message is seen again, it is ignored before it can create duplicate dashboard messages or duplicate AI replies.

### 2. TextBee polling uses a cursor

Polling no longer repeatedly imports old TextBee inbox history.

The app stores:

- `textbee_poll_cursor`

On startup, when `TEXTBEE_POLL_IGNORE_HISTORY_ON_START=true`, the cursor starts at the current time, so old TextBee messages are skipped.

### 3. Delete now archives

Dashboard delete now archives a lead instead of hard-deleting it. This keeps message/provider IDs for dedupe and prevents old TextBee inbox messages from coming back as fresh leads.

### 4. Deleted-lead suppression is retired

The old suppression system caused valid messages to get blocked. v3 uses durable event/message dedupe instead.

### 5. New testing endpoint

Clear the SMS event ledger and TextBee poll cursor if needed:

POST /admin/api/testing/clear-sms-event-ledger

## Recommended TextBee settings

```env
TEXTBEE_INBOUND_POLLING_ENABLED=true
TEXTBEE_INBOUND_POLLING_INTERVAL_SECONDS=30
TEXTBEE_INBOUND_POLLING_LIMIT=10
TEXTBEE_POLL_IGNORE_HISTORY_ON_START=true
TEXTBEE_POLL_LOOKBACK_SECONDS=0
TEXTBEE_POLL_REQUIRE_RECEIVED_AT=true
```

## Expected behavior

- Initial missed-call SMS sends once.
- Customer reply saves once.
- AI assistant fires once.
- Webhook + polling duplicates are ignored by event ledger.
- Old TextBee inbox history is skipped by cursor.
