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
| Trigger | Fires when |
|---|---|
| on_create | After insert |
| on_update | After update |
| on_create_or_update | Either save |
| on_delete | Before/after delete (configured per rule) |
| scheduled | Celery 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:
| Signal | Typical use |
|---|---|
| company_created | Seed stages, defaults, demo data |
| company_currency_changed | Bulk-update MoneyField rows |
| lead_stage_created | Initialize opportunity pipeline |
| opp_stage_created | Opportunity pipeline setup |
| pre_logout_signal | Cleanup, analytics |
| pre_login_render_signal | Login 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:
| Model | Role |
|---|---|
| WorkflowRule | ContentType + create/edit triggers |
| WorkflowCondition | Field/operator / value rows |
| WorkflowAction | Update field, assign task, email, notification |
| WorkflowTimeTriggerAction | Celery-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:
- Admin defines approval template (steps, approvers, conditions)
- User submits record for approval
- State machine blocks edits until approved/rejected
- 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.