Appearance
Queue Jobs
Background work is processed by the BullMQ worker service (services/worker). The API enqueues jobs; the worker processes them independently.
Queues
| Queue name | Constant | Purpose |
|---|---|---|
invoice-processing | QUEUE_NAMES.INVOICE_PROCESSING | OCR extraction for uploaded invoices |
email-sync | QUEUE_NAMES.EMAIL_SYNC | IMAP inbox polling to import invoices |
automation | QUEUE_NAMES.AUTOMATION | Per-user automation triggers |
webhook-delivery | QUEUE_NAMES.WEBHOOK_DELIVERY | Deliver webhook payloads to subscriber URLs |
Job data interfaces
typescript
// invoice-processing
interface InvoiceProcessingJobData {
invoiceId: string
tenantId: string
}
// email-sync
interface EmailSyncJobData {
tenantId: string
}
// automation
interface AutomationJobData {
tenantId: string
tenantUserId: string
}
// webhook-delivery
interface WebhookDeliveryJobData {
webhookId: string
webhookDeliveryId: string
tenantId: string
event: string
url: string
secret: string | null
payload: Record<string, unknown>
}Invoice processing flow
- Worker receives
InvoiceProcessingJobDatafrom theinvoice-processingqueue - Downloads the invoice file from RustFS using the presigned URL
- Sends the image to Google Cloud Vision API for OCR text extraction
- Passes the OCR text to OpenAI for structured data extraction (amount, date, invoice number, etc.)
- Updates the
Invoicerecord in the database with the extracted fields - Sets
invoice.statustoprocessed(orerrorif any step fails)
Webhook delivery flow
- An API event (e.g.
entry.approved) fires — the webhook service looks up active subscriptions for the event - Creates a
WebhookDeliveryrecord and enqueues awebhook-deliveryjob - Worker POSTs the payload to the subscriber's URL
- If a
secretis set, signs the payload with HMAC-SHA256 and includes the signature in theX-Signature-256header - Updates
WebhookDeliverywith the response status and duration
Retry behaviour
Jobs are retried with exponential backoff on failure. Failed jobs remain in the queue's failed list and can be inspected or retried via the BullMQ dashboard (if configured).