← Back to portfolioCampaignBridge guide
Download runnable project ↓# CampaignBridge
A runnable voicebot campaign integration portfolio project by **Aisosa Noze-Otote**. All accounts and outcomes are synthetic; no employer code, customer data or proprietary systems are included.
## What it demonstrates
Customer account input → validation → SQLite campaign records → HTTP call-outcome webhook → atomic record update and outbox entry → simulated follow-up delivery.
The integration boundary uses traditional, deterministic automation: account balances, record matching, duplicate detection and delivery decisions must be predictable. An upstream conversational AI could classify a call, but this project receives a structured outcome; it does not claim to run a voice agent or LLM.
## Run
Python 3.10 or newer; no third-party dependencies.
```sh
python3 server.py
```
Open **http://127.0.0.1:8510**. Keep the terminal running while using the app. Data survives restarts in `demo.sqlite3`, which is excluded from Git. For a fresh independent walkthrough, run `python3 server.py --db fresh-demo.sqlite3 --port 8511` and open port 8511.
```sh
python3 -m unittest discover -s tests -v
```
## Walkthrough (about two minutes)
1. Load three sample accounts. Balances are integer minor units, not floats.
2. Send the promise-to-pay outcome. The account changes and one pending follow-up appears.
3. Replay the same event. The response marks it as a duplicate; there is still only one job.
4. Simulate destination failure. The job becomes `retry`, with its error and attempt count retained.
5. Wait two seconds, then deliver due follow-ups. One simulated receipt appears.
6. Send an invalid account event. The API returns 400; no event or follow-up is created.
Use the live app for the walkthrough; the portfolio's animated preview is illustrative and runs in the browser only.
## API
POST requests require JSON and `Authorization: Bearer local-demo-token`. Override via `CAMPAIGN_TOKEN` and enter the same value in the UI. The default token is a public demo value, not a secret.
| Endpoint | Input / result |
|---|---|
| `POST /api/import` | Array of `{id, name, balance_cents}`; validates entire batch before writing. Reimport updates account fields and preserves outcome. |
| `POST /api/webhook` | `{event_id, customer_id, outcome}`; supported outcomes: `promise_to_pay`, `needs_help`, `declined`. |
| `POST /api/dispatch` | `{fail: true}` simulates HTTP 503; `{}` delivers due jobs to the local mock. |
| `GET /api/state` | Local inspection of customers, events, outbox and mock receipts. |
Replaying an identical event is safe. Reusing its ID with a changed payload returns **409**. A missing account, invalid balance or unsupported outcome returns **400**. `declined` does not create a follow-up. Outcome events never mark a balance paid: a promise is not verified payment.
## Reliability design
- Account update, event insertion and outbox insertion share one SQLite transaction.
- A write lock protects duplicate detection. Event IDs and payload hashes distinguish retries from conflicting data.
- Failed simulated deliveries retry after 2 and 4 seconds; a third failure goes to `dead_letter` for operator review.
- The mock destination deduplicates receipts by event ID. A real external destination must also support an idempotency key to avoid duplicate effects after ambiguous network failures.
- Tests cover rollback, duplicate events, conflicting payloads, declined outcomes, retries, dead letters, persistence and real local HTTP requests.
## Scope and limitations
This is a local, single-process demonstration. The destination is a database-backed simulation, **not a live CRM, SMS service or bank integration**. It has no webhook signature verification, production identity/authorization, retention policy, background worker, monitoring or TLS. State inspection is unauthenticated on loopback. Do not expose the server to a network or load personal data.
Events are processed in arrival order; a production campaign needs call IDs, ordering/version rules and consent/suppression checks. The demo queues follow-ups per unique event; distinct event IDs for the same call are not deduplicated. Retry dispatch is manual for demonstration. Do not infer production readiness or commercial performance from this project.
## Interview explanation
“I used a structured webhook to connect campaign outcomes to downstream work. Validation prevents missing account data entering the workflow. A transaction keeps account updates and the outbox consistent. Idempotency handles repeated events, while backoff and a dead-letter state make failure visible. The destination is mocked so the demo is safe and repeatable.”