Skip to content

ADR-008 — Approval Request Lifecycle

Status: Accepted
Date: 2026-05-21

Decision

Schema

approval_requests
────────────────────────────────────────────────
id              uuid
tenant_id       → tenants.id
type            'reply' | 'refund' | 'cancel' | 'note' | 'resolve' | 'assign'
channel         'chatwoot' | 'freshchat' | null
conversation_id string (channel-side conversation ID, nullable)
payload         jsonb  — full action parameters
drafted_by      'agent'
status          pending | approved | rejected | failed
approved_by     → users.id (null until actioned)
actioned_at     timestamp (null until approved or rejected)
executed_at     timestamp (null until ExecuteApprovalJob completes)
error           text (null unless status = failed)
expires_at      timestamp

State Machine

              ┌─ approved ──→ ExecuteApprovalJob ──→ executed_at set
pending ──────┤                                  └──→ status = failed, error set
              ├─ rejected  (no job fired)
              └─ [expires_at < now] → auto-rejected by scheduler

Expiry

24 hours. A pending approval older than 24 hours is auto-rejected. A reply stale by a day should not be sent — the conversation context will have changed.

Notifications

When the agent creates a pending approval:

  1. The draft reply is posted as an internal note on the Chatwoot conversation (visible to agents only, not the customer)
  2. The Opsome approval dashboard shows the pending queue

No email or SMS notifications in v1. The internal note acts as the push notification to the store owner who is already working in Chatwoot.

Consequences

  • A scheduler job runs periodically to expire stale approvals
  • ExecuteApprovalJob checks status === pending before executing to prevent double-execution
  • Failed executions set status = failed + error text; the approval can be retried manually from the dashboard