Horilla CRM ships a universal automation engine with five trigger types — on create, update, create-or-update, delete, and scheduled — plus a generic approvals process any model can plug into.

This post is Part 7 of 28 in the Horilla CRM Technical Blog series.

What is Horilla CRM automations and workflows?

Horilla CRM automations and workflows enable you to:

  • ContentType-based triggers on any registered model
  • AutomationCondition with AND/OR logic
  • Email, notification, or both as delivery
  • Approvals engine for multi-step sign-off
  • Celery async execution when enabled

Automation engine overview

horilla.contrib.automations defines rules that fire on model lifecycle events or schedules.

# horilla/contrib/automations/models.py (excerpt)
class HorillaAutomation(HorillaCoreModel):
    choices = [
        ("on_create", "On Create"),
        ("on_update", "On Update"),
        ("on_create_or_update", "Both Create and Update"),
        ("on_delete", "On Delete"),
        ("scheduled", "Scheduled (time-based)"),
    ]
    title = models.CharField(max_length=256, unique=True)
    model = models.ForeignKey(
        HorillaContentType,
        on_delete=models.CASCADE,
        limit_choices_to=limit_content_types("automation_models"),
    )
    mail_to = models.TextField(...)  # self, instance.owner.email, ...

Five trigger types

TriggerFires when
on_createAfter insert
on_updateAfter update
on_create_or_updateEither save
on_deleteBefore/after delete (configured per rule)
scheduledCelery Beat / time-based (deduped via run logs)

Delivery channels

  • Email via HorillaMailManager
  • In-app notification via Channels
  • Both

Templates link to HorillaMailTemplate and NotificationTemplate.

Conditions

AutomationCondition rows define field + operator + value with AND/OR ordering — only send when conditions match.

CONDITIONS = [
    ("equal", "Equal (==)"),
    ("notequal", "Not Equal (!=)"),
    ("lt", "Less Than (<)"),
    ("gt", "Greater Than (>)"),
    ("icontains", "Contains"),
    ...
]

Example: “When Lead is created AND lead_source equals website AND annual_revenue > 100000 → notify sales manager.”

ContentType-based universal signals

Unlike string-based model paths, automations reference HorillaContentType. Universal post_save / pre_delete handlers dispatch to matching rules for any registered model.

Register models for automation in registration.py:

register_model_for_feature(
    app_label="leads",
    model_name="Lead",
    all=True,
    features=["workflow_models"],  # among others
)

limit_content_types(“automation_models”) restricts the admin dropdown to eligible models.

Async execution

USE_ASYNC_AUTOMATIONS in settings routes heavy automations to Celery — email bursts and scheduled jobs do not block request threads.

AutomationRunLog tracks scheduled runs for deduplication and auditing.

Custom CRM signals

Module-specific business events complement generic ORM signals:

SignalTypical use
company_createdSeed stages, defaults, demo data
company_currency_changedBulk-update MoneyField rows
lead_stage_createdInitialize opportunity pipeline
opp_stage_createdOpportunity pipeline setup
pre_logout_signalCleanup, analytics
pre_login_render_signalLogin page branding
# horilla_crm/leads/signals.py
from horilla.contrib.core.signals import company_created
 
@receiver(company_created)
def handle_company_created(sender, instance, request, view, is_new, **kwargs):
    if is_new:
        url = reverse_lazy("leads:load_lead_stages", kwargs={"company_id": instance.id})
        ...

Use custom signals for domain orchestration; use automations for user-configurable no-code rules.

Workflow rules (horilla.contrib.workflow)

Distinct from automations (user-facing mail/notification rules), the workflow app runs record-triggered rules with conditions and immediate actions:

ModelRole
WorkflowRuleContentType + create/edit triggers
WorkflowConditionField/operator / value rows
WorkflowActionUpdate field, assign task, email, notification
WorkflowTimeTriggerActionCelery-scheduled relative triggers

Register models with features=[“workflow_models”]. Execution lives in horilla/contrib/workflow/methods.py (dispatched on post_save). Owner resolution for assign_task tries owner, assigned_to, created_by, then lead_owner.

Settings → Automations → Workflow Rules.

Approvals (horilla.contrib.process.approvals)

Generic multi-step approval engine — attaches to any model via ContentType.

Enabled when registered:

register_model_for_feature(
    app_label="leads",
    model_name="Lead",
    features=["approval_models"],
)

Typical flow:

  1. Admin defines approval template (steps, approvers, conditions)
  2. User submits record for approval
  3. State machine blocks edits until approved/rejected
  4. Notifications fire per step

Works the same for Opportunities, Expenses, or custom modules once registered.

Reviews (horilla.contrib.process.reviews)

Peer/manager review cycles — useful for QA on quotes, campaign briefs, or custom objects.

features=["reviews_models"]

Reviews plug into detail tabs via the feature registry — no per-model view fork required.

Combine workflow rules with:

  • Field permissions (lock fields while in review)
  • HTMX status badges on kanban cards
  • Audit log for compliance

Lead scoring (domain example)

Leads register scoring feature. Rules in horilla_crm/scoring_rules recalculate on criterion changes:

# leads/signals.py
from horilla_crm.scoring_rules.utils import compute_score
 
@receiver(post_save, sender=Lead)
def update_lead_score(...):
    compute_score(instance)

Platform pattern: signals for deterministic code, automations for admin-configurable rules.

Architecture: how pieces connect

flowchart TB
    subgraph Registration
        R[registration.py]
    end
    R --> A[FEATURE_REGISTRY]
    A --> B[Automations]
    A --> C[Approvals]
    A --> D[Reviews]
    A --> E[Global Search / Import / Export]
    F[ORM save/delete] --> B
    F --> G[Custom signals]
    G --> H[Cross-module handlers]
    B --> I[Mail / Notifications]
    B --> J[Celery Beat scheduled]

Operational checklist

  • Model registered with correct features / all=True
  • Test automation with mail_to=self in staging
  • Scheduled rules have run log monitoring
  • Approval templates assigned per company
  • Celery worker + beat running in production

Benefits of Automations in Horilla CRM

  • Admin-configurable workflows without redeploying code
  • Cross-module reactions to data changes
  • Audit via AutomationRunLog
  • Approvals on any feature-registered model

Automations turn Horilla CRM from a static database into a reactive platform. Register models in registration.py, then configure rules in the admin or code.

Continue the series

Previous: Part 6 — How to Extend Horilla CRM Without Forking the Core Framework

Next: Part 8 — Build Your First Horilla CRM Module in One Session (Capstone Tutorial)

More posts are at Horilla Blogs; share feedback on GitHub.

Share this article