bublliodocs
Architecture

Automations: Current Behavior

Bubllio currently supports one small automation: when a company is created, queue a configured email action and record the result. Celery workers execute jobs from RabbitMQ, while a database outbox preserves recoverable

Bubllio currently supports one small automation: when a company is created, queue a configured email action and record the result. Celery workers execute jobs from RabbitMQ, while a database outbox preserves recoverable job state.

This page describes implemented behavior. The workflow roadmap describes the future visual builder; it is not an API contract or a shipped feature. Start with the automation walkthrough to try it.

Vocabulary

TermMeaningCurrent example
EventSomething that happened in the CRMA company was created
TriggerEvent name a rule listens forcompany.created
AutomationSaved rule belonging to one organizationNotify accounting
ActionOperation the rule performssend_email
ConfigurationSettings for that operationRecipients, subject, body
PayloadData describing this occurrenceCompany ID, name, lifecycle stage
RunRecorded result of one execution attemptsuccess or failed

Creating an automation saves a rule. It does not execute it or replay companies created earlier. Each later matching event can execute it again.

What Works Today

CapabilityCurrent behavior
Automatically dispatched triggercompany.created only
ActionOne send_email per automation
Rule selectionSame organization, matching trigger, is_active=True
ExecutionAsynchronous through Celery/RabbitMQ and the database outbox
Email contentFixed recipients, subject and body from action_config
Email transportGlobal Django backend; currently console output
HistoryA queued run followed by processing and a terminal result
APIList/create rules, list runs, manually execute a rule
AdministrationDjango admin can edit existing rules and their active flag

There is no visual editor, contact-creation action, template interpolation, condition, multi-step flow, wait state, or user-defined schedule. The API has no automation detail, update, or delete route.

Declared Triggers Are Not All Connected

events.py declares these choices, which the API currently accepts:

TriggerAutomatically emitted by current code?
company.createdYes, from Company post_save
company.updatedNo
company.lifecycle_stage_changedNo
contact.createdNo
contact.updatedNo
organization.createdNo
organization.updatedNo

Saving an unwired choice does not make it work. A manual test can still execute its action because it bypasses event dispatch. Restricting selectable triggers to implemented events is a planned correction, not a completed change.

Execution and Code Map

New Company saved through API, admin, or ordinary ORM save
  -> companies/signals.py: post_save(created=True)
  -> dispatch_company_created(company): build payload
  -> dispatch_automation_event(...): select active rules in this organization
  -> transaction.on_commit(...): dispatch matching rules after the save commits
  -> queue_automation_run(...): create queued run and durable outbox row
  -> Celery worker: claim the outbox row and execute the action
  -> _send_email(...): Django send_mail using global backend
  -> AutomationRun and outbox: persist terminal state

An update does not emit company.created. Bulk operations such as bulk_create do not take this signal path. With no matching active rule, no run is created. If several rules match, all are queued; worker concurrency determines execution order.

FileResponsibility and reason
companies/apps.pyImports signals in ready() to register the receiver
companies/signals.pyDetects creation outside HTTP views too
automations/events.pyCentralizes event names and model choices
automations/models.pyStores rules and results, with UUID identifiers
automations/serializers.pyValidates API configuration and shapes responses
automations/services.pySelects rules and performs actions outside HTTP concerns
automations/views.pyChecks organization access and coordinates requests
automations/urls.pyExposes the implemented endpoint operations
automations/admin.pyRegisters rules and run history in Django admin
automations/tests.pyCurrently tests API authorization and tenant scoping
automations/migrations/0001_initial.pyInitial rule and run schema

Stored Data

Automation holds organization, name, trigger, action_type, action_config, is_active, and timestamps. The organization comes from the authorized URL context, not the request body.

Example create body:

{
  "name": "Notify accounting",
  "trigger": "company.created",
  "action_type": "send_email",
  "action_config": {
    "to": ["accounting@example.com"],
    "subject": "New company",
    "body": "A company was added to Bubllio CRM."
  },
  "is_active": true
}

When configuration validation runs, to must contain at least one valid email address and subject and body must be nonblank. from_email is optional. JSON allows action-specific settings; it does not make arbitrary actions executable. Only send_email is implemented.

The model permits a default empty configuration. Omitted configuration or records created outside the API can therefore reach execution without required keys and fail. Model/admin saves do not use the DRF action serializer.

The company event payload is:

{
  "company_id": "<company UUID>",
  "company_name": "Acme Inc",
  "lifecycle_stage": "lead"
}

Payload is stored for inspection; it is not used to select recipients or render email text. A string such as {{ company_name }} remains literal text.

AutomationRun stores automation, trigger, status, payload, error_message, and created_at. It has no duration, attempts counter, per-step results, or snapshot of the rule configuration. Deleting an automation cascades to its runs; deleting its organization cascades to both.

StatusMeaning in current code
queuedDurable outbox work exists and is waiting for a worker
processingA worker has claimed the run
successAction returned without raising an exception
failedExecution raised an exception; its string is recorded
skippedExecutor encountered an unsupported action type

skipped is a defensive executor path; normal API validation allows only the known action type. Inactive rules are filtered out, not logged as skipped. success does not prove inbox delivery; the sender's numeric return value is not checked.

API and Manual Tests

All routes are below /api/v1/organizations/<organization_id>/:

Method and suffixAccessResult
GET automations/Any organization roleRules in this organization
POST automations/Owner/adminCreate rule; HTTP 201
GET automations/runs/Any organization roleRuns, newest first
POST automations/<automation_id>/test/Owner/adminExecute action; HTTP 201 with result

Superuser access and inaccessible-organization responses follow the shared authorization rules.

The test endpoint queues a real action, not a dry run. It:

  • does not create a company or verify the event signal;
  • executes even when is_active=False or the selected trigger is unwired;
  • records supplied payload, or a default test payload when it is falsy;
  • returns status, run, and a console-email dev_note;
  • returns HTTP 201 with the initial queued state.

Clients must inspect the response status field. To test the complete event path, create a new company after creating the rule and inspect run history.

Email Boundary

Automation emails call Django's global send_mail. The checked-in backend prints messages to the console. Organization SMTP accounts have a separate real-send test endpoint and are not selected by automation execution, even when marked default. See Email Sending.

Reliability and Review Findings

This asynchronous foundation has known limitations:

  • Delivery is at-least-once. A worker crash after sending but before recording success can produce a duplicate email.
  • A database error while creating the run/outbox can still escape to the caller.
  • Failed outbox jobs retry up to six attempts; there is not yet a user-facing retry control or dead-letter dashboard.
  • Action exception strings are stored as-is; run history is not a sanitized audit log and is readable by organization members.
  • Existing automation tests cover authorization/scoping, but do not establish end-to-end signal execution, delivery, inactive-rule filtering, or recovery.

The roadmap prioritizes these gaps while keeping the initial implementation within the existing Django stack.