Automation rules
An automation rule tells BottleCRM "when this happens to a record, and these things are true, do
that". When a deal is won, create a kick-off task for its assignees and tell the account's team.
When a lead arrives from a web form, assign it to the next person in a rotation. Rules arrived in
django-crm 1.21.0, with their screens on the web and the phone in 1.22.0. Everything lives in
backend/automation/, and rules are managed through the API at /api/automation/.
Who can use them
Only org admins can build, read, change, try or delete rules, and that includes the list and the run
history: runs name records and people a member may not be allowed to see. A member gets 403 on
every route.
Rules are managed from a signed-in session only. A personal access token or the org API key is
refused at /api/automation/ whatever scopes it carries, because a rule keeps acting after the token
that wrote it is revoked. Creating one is the same kind of act as creating a webhook.
Building rules in the apps
Admins build rules under Settings, Automation, in the web app and in the phone app. The row is hidden from members.
- The list shows every rule in order with its status (on, off, paused, not running, or needs attention), the reason beneath when there is one, and its runs and failures over the last seven days. Up and down arrows change a rule's place. New rule starts one: pick a name and the kind of record to watch, then save. A rule is switched on or off in the editor (Save and switch on, Switch off). On a paused rule the button reads Save and take over: switching it on makes you its owner.
- The editor reads as the rule does: When, Only if, Then. Fields, options, people and webhooks come from your org, so only what it offers can be chosen. A value that was valid when saved and no longer is stays visible, marked "(no longer available)".
- Try it on a record picks a record and shows what a run would do, writing nothing. It uses the saved rule, so it is disabled while the editor has unsaved changes. It shows what the actions would do and does not check the trigger or the conditions.
- Run history lists the rule's runs, newest first. Each run opens to its steps.
What a rule is
| Part | Meaning |
|---|---|
| Name | What the rule is called in lists, notices and the activity feed. |
| Object | The kind of record it watches: lead, contact, account, deal, ticket or task. Fixed once the rule exists. |
| When | The trigger: see Triggers below. |
| Only if | Up to 20 conditions, matched all (every one holds) or any (at least one does). One level, no groups. No conditions always holds. |
| Then | Up to 10 actions, run in order: see Actions below. |
| Owner | The admin who last switched it on. The rule acts as this person. |
Rules are checked against what the org offers when they are saved and again before every run, so a rule cannot set a field or an option its record's edit form would refuse, and cannot name a person, webhook or link outside its org.
A rule that was valid and later breaks (its field was switched off, an option was removed, its person was deactivated) stays saved and switched on. Its runs fail with the reason, the list shows it as Needs attention, and saving or switching it on again checks it afresh. Renaming it or switching it off always works.
Fields
| Object | Fields a rule can read |
|---|---|
| Lead | title, first and last name, email, phone, company, job title, website, city, country, status, source, industry, rating, stage (read only), estimated value, probability, expected close date, next follow-up, assigned to |
| Contact | first and last name, email, phone, company, job title, department, city, country, do not call, assigned to |
| Account | name, email, phone, website, city, country, industry, employees, annual revenue, assigned to |
| Deal | name, stage, outcome (read only), type, source, amount, probability, close date, assigned to |
| Ticket | subject, status, priority, type, closed on, assigned to |
| Task | title, status, priority, due date, assigned to |
Your org's active custom fields of types text, textarea, number, date, dropdown and checkbox are added to their object. A field can be set by a rule exactly when the object's own edit form writes it. Server-owned fields (ids, the org, who created a record, timestamps) are never offered.
- Deal stage lists every pipeline's stages, grouped by pipeline. A rule can only move a deal to a stage of the deal's own pipeline.
- Deal outcome is open, won or lost, read from the kind of the deal's stage, so "outcome changes to won" fires for a win in any pipeline, whatever the stage is called.
- Lead status offers converted to read but not to set: converting a lead creates an account, a contact and a deal, and is never an edit. Ticket status treats Duplicate the same way, since only a merge sets it.
- Assigned to can be read and set, but a change of people cannot be a trigger.
Actions can also reach the record's one parent: a deal's account, a ticket's account, or the lead, account, deal or ticket a task belongs to. Leads, contacts and accounts have no parent.
Triggers
| Trigger | Fires when |
|---|---|
| Created | A record of the object is created. |
| Updated | Any field a rule can read changed. |
| A field changes | That field changed, to anything. |
| A field changes to a value | That field moved into the value. |
"Changes to" fires only on the move into the value: the old value differed and the new one matches. Later edits to a won deal do not run "outcome changes to won" again.
What sets a rule off: any save made through the app. That covers the web and phone apps, the REST API and access tokens, a card moved to another column on a board, a public web form submission, an emailed ticket, and saves made by background jobs. A "created" rule fires on web forms and inbound email even though nobody is signed in.
Rules run as the request finishes, after everything in it was saved, so the page that reloads after a save already shows what the rules did, and a condition such as "assigned to is not empty" sees assignees set in the same save. Ticket routing runs first, so a "ticket created" rule sees the routed assignees. A change that was rolled back sets nothing off.
What does not set rules off:
- Spreadsheet imports of leads, contacts and tickets, and industry pack sample data as the pack creates it. A 2,000-row import with an active "lead created: create a task" rule creates no tasks.
- The
seed_datamanagement command. - Bulk ticket edits. Closing 200 tickets at once runs no rules.
- Bulk database operations that skip save signals, such as the moves inside a ticket merge.
- Changes to assignees, tags or teams on their own. Conditions can still read them.
- Deleting a record. Webhooks cover deletes.
Conditions
| Type | Operators |
|---|---|
| Text | is, is not, contains, does not contain, is empty, is not empty |
| Number | is, is not, is more than, is less than, is empty, is not empty |
| Date | is, is not, is after, is before, is empty, is not empty |
| Choice | is, is not, is any of, is none of, is empty, is not empty |
| Yes or no | is true, is false |
| Person, people | is exactly, includes, does not include, is empty, is not empty |
- Text comparison with contains ignores case. Dates are fixed dates; relative dates ("in the next 7 days") are not offered.
- was and was not compare the value from before this save, on any field except people, and only for an "updated" trigger.
- There is no pattern or regular expression operator. Text you or your customers type is only ever compared, never evaluated.
Actions
A rule's actions run in order inside one transaction, so a later action sees what an earlier one did: assign someone, then notify the record's assignees, and the notice reaches the new assignee.
{name} is the only placeholder, in task titles and notify messages. It is replaced by the record's
display name as plain text. Nothing is rendered as a template, markdown or HTML.
- Set a field on the record or its parent. Clearing is allowed. The write goes through the object's own edit path as the rule's owner, so it passes exactly the checks the edit form applies: a deal moved to a won stage still needs an amount and a close date, and a lead cannot be converted. A record with no parent is a skipped step, not a failure.
- Assign to one person (replace), or in turn between 2 and 20 people (rotate), skipping anyone no longer active. Either way the record's current assignees are replaced, and the newly assigned get the module's usual assignment email.
- Create a task with a title of up to 200 characters, an optional due date 0 to 365 days from today in the org's timezone, a priority (Low, Medium or High), and assignees: the record's, the parent's, the rule's owner, or one person. It links to the record where a task can.
- Notify the record's assignees, its creator, the parent's assignees, the org's admins, or people you pick, with a message of up to 500 characters. Each gets an in-app notification and an email with a link to the record. Only active members of the org are ever reached; there is no way to send to an outside address.
- Send a webhook to one of the org's own active webhooks with the Signed JSON format, as a
rule.triggeredevent. Signing, the URL rules, retries and the delivery log are the same as for every other webhook.
Notify and create a task reach only people who can open the record. Deals and leads are often private to their assignees, so anyone filtered out is named in the step ("Skipped Meera: cannot open this deal"), and when nobody is left the step is skipped, not failed.
How a run happens
- Order. For each change, every matching switched-on rule on that object runs, in list order. There is no "stop here".
- One rule, one transaction. If any step fails, that rule's writes roll back, the run is recorded as failed with the reason, and the next rule still runs. Emails and webhooks go out only once the rule's transaction commits. A broken rule never blocks or undoes the save that set it off.
- Who it acts as. The rule's owner. Records it creates and fields it sets are credited to the owner, and the activity feed notes the rule's name beside the change.
- Chains. A rule's own writes can set off other rules, up to 3 deep. A rule acts on a given record at most once per chain, and never on a change its own run made.
- Owners who lose their rights. When a rule's owner stops being an admin, is deactivated or leaves the org, their active rules are switched off with a reason and the other admins are told. Another admin takes a rule over by switching it on again.
- Failures reach a person. When a run fails, the rule's owner gets an in-app notice, at most once a day per rule.
Limits
The same on every install:
| Limit | Value |
|---|---|
| Conditions per rule | 20 |
| Actions per rule | 10 |
| Records one action may touch | 100 (more fails the step rather than doing part of it) |
| Chain depth | 3 |
| Runs per org per hour | 1,000 |
| Run history | 30 days |
Runs past the hourly limit do not run. Each rule gets one skipped run per hour that counts them, and the org's admins get one notice for that hour.
The open-source code puts no limit on how many rules are switched on. On the hosted app at app.bottlecrm.io an organisation can have three rules switched on at a time for now; switching on a fourth is refused with the reason, and the list says so.
Runs and notifications
Each run records the record it ran on (with its name at the time), its status, its reason, and one step per action. A run is done, failed (a step failed and nothing it wrote was kept) or skipped (counted past the hourly limit). A step is done, undone (rolled back with its failed rule), failed, or skipped.
Rules send four kinds of in-app notification: a notify action's message, a failed run (to the owner), paused rules (to the other admins) and the hourly limit (to the admins).
The API
All under /api/automation/, admin only, from a signed-in session:
| Method and path | Does |
|---|---|
GET catalogue/ |
objects with their fields, parents and links; operators; action types; the org's active members and Signed JSON webhooks; the limits |
GET rules/ |
the org's rules, each with status, status_text, runs_7d and failures_7d |
POST rules/ |
create, at the end of the list |
GET rules/<id>/ |
one rule |
PATCH rules/<id>/ |
change it (there is no PUT) |
DELETE rules/<id>/ |
delete it and its runs |
POST rules/<id>/try/ |
the steps a run would take on one record, writing nothing |
GET rules/<id>/runs/ |
its runs, newest first, limit/offset paginated |
POST rules/reorder/ |
a new list order: {"ids": [...]} |
Another org's id answers 404. An invalid rule is refused with a 400 whose errors are keyed by
path, such as conditions[2].value, so an editor can mark the row. The headline example, created and
switched on in one request:
{
"name": "Deal won: onboarding",
"object_key": "deal",
"trigger": {"event": "updated", "field": "stage_kind", "to": "won"},
"match": "all",
"conditions": [{"field": "amount", "op": "gt", "value": 0}],
"actions": [
{
"type": "create_task",
"title": "Kick-off call: {name}",
"due_in_days": 2,
"priority": "High",
"assign_to": "record",
"person": null
},
{
"type": "notify",
"to": ["parent_assignees"],
"people": [],
"message": "{name} is won. Time to say hello."
}
],
"is_active": true
}
The full field-by-field reference, with every operator and action body, is
docs/integrations/automation.md in the
Django-CRM repository.
Not built
- Schedules. A rule runs when a record changes, never on a date or after a delay.
- Branching. One level of conditions, all or any, with no groups and no if-else.
- Email to people outside your org.
- Deletes as a trigger.