Workflow recipes
In short: These recipes are building plans for common workflows, regardless of the kind of business. Every recipe says when to use it, which event it starts on, which steps it has (with technical name and English label), which exits go where and what to watch out for. Most recipes include a JSON definition that passes aishaa’s check. A person or an AI assistant adjusts the example values, saves it as a draft, tries it out, and a person publishes it.
Where to find this
You build a recipe in Account > Workflows (role Organisation admin): by hand in the editor, with the Building assistant in the editor, or by an AI assistant that works through the connection with aishaa and saves the draft. The JSON format and the tools for AI assistants are in The workflow definition.
How to design a tailored workflow
- What should happen, and when? Translate the wish into an event (see the table below) and possibly conditions. A condition on the trigger costs nothing and prevents runs that stop at once.
- Does the workflow answer the customer? A workflow with Write the reply (
agent.reply) must start on Message read (message.understood). Put all of the answering of a message in one workflow: build it into the standard workflow Answering customer messages, or pause that one as soon as your answering workflow goes live. Workflows that only do something next to the reply (assigning, labelling, a task) may stand on their own. - Who decides? Let everything that goes out be a proposal first (
mode: "propose", the default) until the organisation trusts the workflow. A question to a colleague is Ask a person (human.request); work without waiting is a Task (task.create). - What if something does not work? Give every exit that means “did not work” (
failed,none,rejected,expired) a route where needed, and use If this fails (onError) where a colleague can still help. - Replace the example values with the organisation’s real ones (see the table further down).
- Write a label for every step (
label, at most 120 characters) in the organisation’s language: what the step does for this business. Colleagues read the workflow by those labels, also on the ticket. - Save as a draft, try it out and let a person publish. Try it out on a few real events; for an answering workflow also with Try it on last week.
| The organisation wants to do something when… | Event | Technical name |
|---|---|---|
| a customer writes and aishaa has understood the message (sorting, replying, assigning) | Message read | message.understood |
| a message comes in, even before it is read | A message comes in | message.received |
| a conversation starts | A conversation starts | ticket.created |
| the status of a conversation changes | A conversation’s status changes | ticket.status_changed |
| a conversation is assigned | A conversation is assigned | ticket.assigned |
| a proposal, question or task has been handled | An action is handled | action.resolved |
| a record (order, file) is linked to a customer | Something is linked to a customer | object.linked |
| a recorded conversation has been transcribed | A conversation is transcribed | recording.transcribed |
| it is time according to a schedule | On a schedule | schedule.due |
Replacing example values
The recipes use example values. They do not work in a real organisation until you replace them.
| In the recipe | What it is | In the editor | For an AI assistant |
|---|---|---|---|
42 at assignee |
The user id of a colleague | Dropdown For | The “Colleagues” list in the guide from workflows_get_guide |
7, 8, 9 at team |
The id of a team (support group) | Dropdown Team | “Teams” |
101 to 105 at label or labels |
The id of a label | Label or Label per option | “Labels” |
"agent": 12 (in the standard workflow) |
The id of an AI agent | Dropdown Agent | “Agents”, with what each AI agent is for |
315, 318 at topicId, 1201 at topicDetailId |
The place on the topic map | Open a run of a message about that topic, click the top box (What started it) and read topicId or topicDetailId in the data of the event |
Not in the guide. Let an admin look up the number in a run, or use trigger.topic and trigger.topicDetail (the names, as on the topic map) |
PLUGIN/LEESACTIE, PLUGIN/ANNULEERACTIE, PLUGIN/HERINNERACTIE |
A read or action of a connected system, as pluginId/operationId |
What to read or Action | “Plugin reads”, “Record reads” and “Plugin actions”, with the schema of the parameters |
order, dossier at kind or recordKind |
The kind of record as the connected system calls it | Kind of record | “Record kinds” |
orderId, invoiceId, status in input |
The parameters of a read or action | The JSON field Parameters | The schema of the read or action |
ophalen.data.items, elk.item.id |
The shape of the connected system’s answer | Try it out and look at the step under Out | Not in the guide: let an admin try it out and pass on the output of the step |
See Data from connected systems in a workflow for the reads and actions.
Recipe 1: Sorting and replying differently per topic
When to use: the organisation wants its own approach per kind of question: its own emphasis in the reply, a label, a team, or a colleague who does something first.
Trigger: Message read (message.understood), without conditions. This is the organisation’s answering workflow: it replaces the standard workflow (pause that one) or you build it into the standard workflow.
Steps:
agent.chooseChoose from a list, label “Decide what the message is about”. Options with what they mean, a label per option, May choose several on. At If this fails: the general reply step.- Per route an
agent.replyWrite the reply with its own Extra instruction for this step. - For
klacht, firstticket.updateUpdate conversation (team and important), then a reply that always remains a proposal (send: "propose"). - For
offerte, firsttask.createTask for sales, then a reply confirming that sales will get in touch. - For other (
other) a generalagent.reply.
Exits: bestelling, retour, klacht, offerte and other of the sorting step; the reply steps end (no routes needed).
Watch out:
- Keep the options short and distinct, and describe well what each option means: the AI agent chooses based on that. If it is in doubt, it chooses other.
- The names at
exitsmust be exactly the same as the options inoptions. - Do not let the AI agent make up information in an Extra instruction for this step that it cannot look up; give it the right skills.
{
"trigger": { "event": "message.understood" },
"steps": [
{ "id": "sort", "block": "agent.choose", "label": "Decide what the message is about",
"inputs": {
"options": ["bestelling", "retour", "klacht", "offerte"],
"explanations": {
"bestelling": "asks about the status, delivery or contents of an order",
"retour": "wants to send something back, exchange it or get a refund",
"klacht": "is unhappy with a product, a colleague or the service",
"offerte": "wants a price or quote for something new"
},
"labels": { "bestelling": 101, "retour": 102, "klacht": 103, "offerte": 104 },
"multiple": true,
"input": "{{trigger.message.text}}"
},
"exits": { "bestelling": "reply-bestelling", "retour": "reply-retour", "klacht": "klacht-team", "offerte": "offerte-taak", "other": "reply" },
"onError": "reply" },
{ "id": "reply-bestelling", "block": "agent.reply", "label": "Answer the question about the order",
"inputs": { "instruction": "Look up the order and state the current status and the expected delivery." } },
{ "id": "reply-retour", "block": "agent.reply", "label": "Explain the return",
"inputs": { "instruction": "Explain the return procedure step by step and state the deadline." } },
{ "id": "klacht-team", "block": "ticket.update", "label": "Give the complaint to the Customer service team",
"inputs": { "team": 7, "important": "yes" },
"next": "reply-klacht" },
{ "id": "reply-klacht", "block": "agent.reply", "label": "Answer the complaint, always as a proposal",
"inputs": { "instruction": "Acknowledge the problem in your own words and name a concrete next step. Do not promise any compensation.", "send": "propose" } },
{ "id": "offerte-taak", "block": "task.create", "label": "Prepare a quote task for sales",
"inputs": { "title": "Quote request in ticket {{trigger.ticket.number}}", "text": "{{trigger.question}}", "assignee": 42, "dueMinutes": 1440 },
"next": "reply-offerte" },
{ "id": "reply-offerte", "block": "agent.reply", "label": "Confirm that sales will get in touch",
"inputs": { "instruction": "Confirm that a colleague from sales will get in touch within one working day. Do not mention any prices yet." } },
{ "id": "reply", "block": "agent.reply", "label": "Answer the message" }
]
}
Recipe 2: Assigning per channel or topic
When to use: conversations must reach the right team, per channel or per topic. This workflow does not reply, so it stands on its own next to the answering workflow.
Trigger: Message read (message.understood). For assigning by topic: with conditions on trigger.topicId (and possibly trigger.topicConfidence).
Steps (per channel):
flow.switchChoose a route on{{trigger.channel}}, with the valuesWhatsAppandEmail.- Per value
ticket.updateUpdate conversation with a Team. The team’s work distribution chooses the colleague.
Exits: WhatsApp, Email and other (no route: the run ends).
Watch out:
- Without For (
assignee), the team’s work distribution chooses the colleague. If you name a colleague, that colleague gets the conversation. - Update conversation only changes what you fill in. What you leave empty or set to Leave as it is (
keep) stays the same. - A condition on the topic id keeps working if someone changes the name of the topic; a condition on
trigger.topic(the name) does not.
{
"trigger": { "event": "message.understood" },
"steps": [
{ "id": "kanaal", "block": "flow.switch", "label": "Choose the team per channel",
"inputs": { "value": "{{trigger.channel}}", "cases": ["WhatsApp", "Email"] },
"exits": { "WhatsApp": "team-chat", "Email": "team-mail" } },
{ "id": "team-chat", "block": "ticket.update", "label": "Give the conversation to the chat team",
"inputs": { "team": 7 } },
{ "id": "team-mail", "block": "ticket.update", "label": "Give the conversation to the e-mail team",
"inputs": { "team": 8 } }
]
}
Assigning by topic, with the conditions in the trigger:
{
"trigger": {
"event": "message.understood",
"conditions": [
{ "path": "trigger.topicId", "op": "eq", "value": 315 },
{ "path": "trigger.topicConfidence", "op": "gte", "value": 70 }
]
},
"steps": [
{ "id": "team", "block": "ticket.update", "label": "Give the conversation to the Billing team",
"inputs": { "team": 9 } }
]
}
Recipe 3: Urgent messages to a colleague with a task
When to use: an urgent message (a deadline, a safety risk, a customer who is stuck) must reach a person quickly, next to the normal reply.
Trigger: Message read (message.understood) with the condition trigger.urgent is true.
Steps:
ticket.updateUpdate conversation: Urgent on Yes and the right Team.task.createTask for the team lead, with the ticket number, the reason ({{trigger.urgencyReason}}) and the question ({{trigger.question}}), deadline in 60 minutes.
Exits: none; both steps continue with Then.
Watch out:
- This workflow does not reply. The reply comes from the answering workflow.
- If you want something different outside opening hours, add a condition on
trigger.withinOpeningHours, or split with a Condition step.
{
"trigger": {
"event": "message.understood",
"conditions": [ { "path": "trigger.urgent", "op": "eq", "value": true } ]
},
"steps": [
{ "id": "markeer", "block": "ticket.update", "label": "Mark the conversation as urgent",
"inputs": { "urgent": "yes", "team": 7 },
"next": "taak" },
{ "id": "taak", "block": "task.create", "label": "Ask the team lead to look within an hour",
"inputs": {
"title": "Urgent: ticket {{trigger.ticket.number}}",
"text": "{{trigger.urgencyReason}} Customer's question: {{trigger.question}}",
"assignee": 42,
"dueMinutes": 60
} }
]
}
Recipe 4: Acknowledge outside opening hours
When to use: a customer who e-mails for the first time outside opening hours immediately gets a confirmation that the message has arrived. This recipe is also in the library as Acknowledge outside opening hours.
Trigger: Message read (message.understood) with the conditions trigger.channel is Email, trigger.isNewConversation is true and trigger.withinOpeningHours is false.
Steps:
message.sendMessage to the customer with the text of the confirmation, How set to Send at once (execute).
Exits: sent, failed, rejected, expired; no routes needed.
Watch out:
- In the Workflow panel, switch on May write next to an answer. It is not in the JSON: it is a property of the workflow. Without that switch, the colleague who sends the reply sees a notice that the customer already received a message from this workflow, and a reply that would be allowed to send by itself still goes as a proposal.
- Send at once goes out under the organisation’s name with the automatic signature, without a colleague seeing it. Choose A colleague reads and sends it (
propose) if you would rather not. - A message from a workflow does not change the status of the conversation. If you want to set the conversation to answered, add
ticket.updatewithstatus: "answered". - You set the opening hours per organisation, see Opening hours.
{
"trigger": {
"event": "message.understood",
"conditions": [
{ "path": "trigger.channel", "op": "eq", "value": "Email" },
{ "path": "trigger.isNewConversation", "op": "eq", "value": true },
{ "path": "trigger.withinOpeningHours", "op": "eq", "value": false }
]
},
"steps": [
{ "id": "send", "block": "message.send", "label": "Send the acknowledgement",
"inputs": {
"message": "Thank you for your message! We are closed at the moment. As soon as we are open again, we will help you as quickly as possible.",
"mode": "execute"
} }
]
}
Recipe 5: Looking up data in a connected system and replying with it
When to use: for one kind of question (for example the status of an order) the organisation wants the reply always to be based on the data from its own system, and a fixed route when nothing or too much is found.
Trigger: Message read (message.understood). This is an answering workflow: build it into the standard workflow or pause that one.
Steps:
flow.conditionCondition ontrigger.topicDetailId: is it about the status of an order? no goes to the general reply.record.findCustomer’s record with Kind of recordorder. It only finds records that belong to the customer on strong evidence (e-mail address or phone number).record.readRead the record’s details on{{zoek.recordId}}.agent.replywith an Extra instruction for this step containing the data ({{details.data}}).- Separate replies for none (ask for the order number) and several (ask which order).
Exits: true/false of the condition; found/none/several of Customer’s record; found/none/failed of Read the record’s details.
Watch out:
- Often this is not needed: an AI agent with the right skills looks up the order itself while it writes. Use this recipe when the route must depend on what is found.
- Read the record’s details only works with a read that the connected system binds to that kind of record.
- An object or list in a text is filled in as JSON. The AI agent can read that; a customer never sees that text.
{
"trigger": { "event": "message.understood" },
"steps": [
{ "id": "orderstatus", "block": "flow.condition", "label": "Is it about the status of an order?",
"inputs": { "conditions": [ { "path": "trigger.topicDetailId", "op": "eq", "value": 1201 } ] },
"exits": { "true": "zoek", "false": "reply" } },
{ "id": "zoek", "block": "record.find", "label": "Find the customer's order",
"inputs": { "kind": "order" },
"exits": { "found": "details", "none": "reply-geen", "several": "reply-meerdere" },
"onError": "reply" },
{ "id": "details", "block": "record.read", "label": "Read the order in the system",
"inputs": { "read": "PLUGIN/LEESACTIE", "record": "{{zoek.recordId}}" },
"exits": { "found": "reply-status", "none": "reply-geen", "failed": "reply" } },
{ "id": "reply-status", "block": "agent.reply", "label": "Reply with the status from the system",
"inputs": { "instruction": "For the status and delivery, only use this data from the system, for order {{zoek.reference}}: {{details.data}}" } },
{ "id": "reply-geen", "block": "agent.reply", "label": "Ask for the order number",
"inputs": { "instruction": "We found no order for this customer. Kindly ask for the order number or the e-mail address used to place the order." } },
{ "id": "reply-meerdere", "block": "agent.reply", "label": "Ask which order is meant",
"inputs": { "instruction": "The customer has several orders. Ask which order it is about." } },
{ "id": "reply", "block": "agent.reply", "label": "Answer the message" }
]
}
Recipe 6: An action in a connected system only after approval
When to use: a customer asks for something that has to happen in a connected system (cancelling, creating a return, rescheduling an appointment) and a colleague must approve the action.
Trigger: Message read (message.understood). Answering workflow: build it into the standard workflow or pause that one.
Steps:
agent.chooseChoose from a list with the optionannuleren.agent.replyWrite the reply confirming that the request is being handled, without promising that it has already happened.record.findCustomer’s record with Kind of recordorder.plugin.actionPlugin action with How set to Propose to a person (propose), the parameters from the record found ({{zoek.itemId}}) and a Summary that the colleague reads. The run waits for the decision, here at most 8 hours.- On executed:
message.sendMessage to the customer as a proposal, with the confirmation. - On rejected:
task.createwith the reason for rejection ({{annuleer.rejectionReason}}). - On failed, expired or if there are no or several orders:
task.createto arrange it manually.
Exits: annuleren/other; drafted/sent of the reply; found/none/several; executed/rejected/failed/expired of the action.
Watch out:
- Write the reply first, and then prepare the action. That way the customer does not wait for the approval.
- The colleague can adjust the parameters in the card before approving. The output
editedsays whether that happened. - If the AI agent itself also has a skill to cancel, it can propose the same action in its reply. Choose one of the two.
- If a colleague approves the proposal unchanged often enough, someone who may change the workflow can choose Do this itself from now on for this step on the ticket.
{
"trigger": { "event": "message.understood" },
"steps": [
{ "id": "sort", "block": "agent.choose", "label": "Does the customer want to cancel an order?",
"inputs": {
"options": ["annuleren"],
"explanations": { "annuleren": "wants to cancel an order that has not been shipped yet" },
"input": "{{trigger.message.text}}"
},
"exits": { "annuleren": "reply-annuleren", "other": "reply" },
"onError": "reply" },
{ "id": "reply-annuleren", "block": "agent.reply", "label": "Confirm that we are handling the cancellation",
"inputs": { "instruction": "Confirm that we are handling the cancellation. Do not say that the order has already been cancelled." },
"exits": { "drafted": "zoek", "sent": "zoek" } },
{ "id": "zoek", "block": "record.find", "label": "Find the customer's order",
"inputs": { "kind": "order" },
"exits": { "found": "annuleer", "none": "handmatig", "several": "handmatig" } },
{ "id": "annuleer", "block": "plugin.action", "label": "Cancel the order in the system",
"inputs": {
"action": "PLUGIN/ANNULEERACTIE",
"mode": "propose",
"input": { "orderId": "{{zoek.itemId}}" },
"summary": "Cancel order {{zoek.reference}} at the customer's request",
"deadlineMinutes": 480
},
"exits": { "executed": "bevestig", "rejected": "afgewezen", "failed": "handmatig", "expired": "handmatig" } },
{ "id": "bevestig", "block": "message.send", "label": "Let the customer know it has been arranged",
"inputs": { "message": "Your order {{zoek.reference}} has been cancelled. You will receive the refund within five working days.", "mode": "propose" } },
{ "id": "afgewezen", "block": "task.create", "label": "Let a colleague explain to the customer why not",
"inputs": { "title": "Cancellation rejected in ticket {{trigger.ticket.number}}", "text": "Reason: {{annuleer.rejectionReason}}. Let the customer know what is possible." } },
{ "id": "handmatig", "block": "task.create", "label": "Ask a colleague to arrange the cancellation themselves",
"inputs": { "title": "Arrange cancellation manually for ticket {{trigger.ticket.number}}", "text": "{{annuleer.error}}" } },
{ "id": "reply", "block": "agent.reply", "label": "Answer the message" }
]
}
Recipe 7: Fetching a list on a schedule and processing it after one approval
When to use: periodic work on a list from a connected system, such as payment reminders or a weekly overview, where one colleague approves the whole list instead of every item.
Trigger: On a schedule (schedule.due), for example every Monday at 8:00.
Steps:
plugin.readFetch from a connected system with the parameters of the read.ai.transformAI step that writes a short overview (outputtext).human.requestAsk a person with the overview under Show and the answersversturenandniet, with button texts.- On
versturen:flow.foreachFor each over the list, with per itemplugin.actionPlugin action on Execute at once. A failed action becomes a task; then the repetition continues with the next item. - After the last item (done):
task.createwith the number ({{elk.count}}).
Exits: found/failed of the read; versturen/niet/expired of the question; each/done of the repetition; failed of the action.
Watch out:
- Do not put a step that waits for a person (a question or an action as a proposal) inside a repetition: then every item waits separately. Ask once beforehand, as here.
- A repetition takes at most 200 items. The output
skippedsays how many were left over. - Create a workflow on a schedule for one organisation, not for the whole account. See Scheduled workflows.
- Never let the AI calculate. You add up amounts or calculate dates with Calculate and format (
data.format).
{
"trigger": { "event": "schedule.due", "schedule": { "every": "week", "at": "08:00", "days": ["monday"] } },
"steps": [
{ "id": "ophalen", "block": "plugin.read", "label": "Fetch the overdue invoices",
"inputs": { "read": "PLUGIN/LEESACTIE", "input": { "status": "overdue" } },
"exits": { "found": "overzicht", "failed": "fout" } },
{ "id": "overzicht", "block": "ai.transform", "label": "Summarise the list",
"inputs": {
"instruction": "Make an overview of the invoices: per line the customer, amount and days overdue. At most twenty lines.",
"input": { "facturen": "{{ophalen.data}}" },
"output": { "type": "object", "properties": { "text": { "type": "string" } } }
},
"next": "akkoord" },
{ "id": "akkoord", "block": "human.request", "label": "Ask whether the reminders may go out",
"inputs": {
"title": "Send payment reminders?",
"text": "These invoices are outstanding. May they all get a reminder?",
"exits": ["versturen", "niet"],
"exitLabels": { "versturen": "Send reminders", "niet": "Not now" },
"show": { "Overview": "{{overzicht.text}}" },
"assignee": 42,
"deadlineMinutes": 1440
},
"exits": { "versturen": "elk" } },
{ "id": "elk", "block": "flow.foreach", "label": "For each invoice",
"inputs": { "items": "{{ophalen.data.items}}" },
"exits": { "each": "herinner", "done": "klaar" } },
{ "id": "herinner", "block": "plugin.action", "label": "Send a reminder for this invoice",
"inputs": { "action": "PLUGIN/HERINNERACTIE", "mode": "execute", "input": { "invoiceId": "{{elk.item.id}}" } },
"exits": { "failed": "fout-factuur" } },
{ "id": "fout-factuur", "block": "task.create", "label": "Report a reminder that did not work",
"inputs": { "title": "Reminder failed for invoice {{elk.item.id}}", "text": "{{herinner.error}}" } },
{ "id": "klaar", "block": "task.create", "label": "Report that the reminders have been sent",
"inputs": { "title": "{{elk.count}} payment reminders processed" } },
{ "id": "fout", "block": "task.create", "label": "Report that fetching failed",
"inputs": { "title": "Overdue invoices could not be fetched", "text": "{{ophalen.error}}" } }
]
}
Recipe 8: Follow-up after a recorded conversation
When to use: after a conversation with a customer that has been recorded and transcribed, a report must be made and the colleague who held the conversation must check it.
Trigger: A conversation is transcribed (recording.transcribed).
Steps:
ai.transformAI step on{{trigger.recording.transcript}}, with the report (text) and the action points (actiepunten) as output.document.createMake a document: a PDF of the report, stored with the customer (with the organisation if there is no linked customer).task.createTask for whoever recorded the conversation ({{trigger.recordedByUserId}}), with the action points.
Exits: none; all steps continue with Then.
Watch out:
- If you want to send the report to a file in a connected system, add
record.findand aplugin.actionas a proposal. A plugin action in the same run takes the document along. See the example in Data from connected systems in a workflow. - A recording is confidential. In the runs, only those who may see the recording see what the steps read and wrote.
- If the person who recorded has no access to the organisation’s tickets, the task is for everyone.
{
"trigger": { "event": "recording.transcribed" },
"steps": [
{ "id": "verslag", "block": "ai.transform", "label": "Write the conversation report",
"inputs": {
"instruction": "Write a businesslike report of this conversation: reason, what was discussed, agreements and action points with who picks them up. Start every heading with #.",
"input": { "transcript": "{{trigger.recording.transcript}}" },
"output": {
"type": "object",
"properties": {
"text": { "type": "string" },
"actiepunten": { "type": "string" }
}
}
},
"next": "document" },
{ "id": "document", "block": "document.create", "label": "Make a PDF of the report",
"inputs": { "title": "Conversation report", "text": "{{verslag.text}}" },
"next": "controle" },
{ "id": "controle", "block": "task.create", "label": "Ask whoever recorded to check the report",
"inputs": {
"title": "Check the conversation report",
"text": "The report is stored as a PDF with the customer. Action points: {{verslag.actiepunten}}",
"assignee": "{{trigger.recordedByUserId}}",
"dueMinutes": 1440
} }
]
}
Recipe 9: Checking a linked record
When to use: as soon as a record from a connected system (a file, an order) is linked to a customer, someone needs to know whether it is complete.
Trigger: Something is linked to a customer (object.linked) with the conditions trigger.recordKind is dossier and trigger.strongEvidence is true.
Steps:
record.readRead the record’s details on{{trigger.record.recordId}}.ai.transformAI step that decides whether the file is complete (outputcompleetas yes/no andontbreektas text).flow.conditionCondition oncheck.compleet.- On no:
task.createwith what is missing.
Exits: found/none/failed of the read; true/false of the condition.
Watch out:
strongEvidenceistrueif the record was found by e-mail address or phone number, or linked by a person. A record found only by name may belong to someone with the same name.- This run usually does not belong to a conversation. So a Message to the customer is not possible here; prepare a task.
- The AI step only uses what is in the data. Rather leave missing fields empty than guess: that is also in the instruction of every AI step.
{
"trigger": {
"event": "object.linked",
"conditions": [
{ "path": "trigger.recordKind", "op": "eq", "value": "dossier" },
{ "path": "trigger.strongEvidence", "op": "eq", "value": true }
]
},
"steps": [
{ "id": "lees", "block": "record.read", "label": "Read the file in the system",
"inputs": { "read": "PLUGIN/LEESACTIE", "record": "{{trigger.record.recordId}}" },
"exits": { "found": "check", "failed": "fout" } },
{ "id": "check", "block": "ai.transform", "label": "Decide which data is still missing",
"inputs": {
"instruction": "Decide whether the file is complete. A complete file has a date of birth, an address and a signed agreement. State what is missing.",
"input": { "dossier": "{{lees.data}}" },
"output": {
"type": "object",
"properties": { "compleet": { "type": "boolean" }, "ontbreekt": { "type": "string" } }
}
},
"next": "compleet" },
{ "id": "compleet", "block": "flow.condition", "label": "Is the file complete?",
"inputs": { "conditions": [ { "path": "check.compleet", "op": "eq", "value": true } ] },
"exits": { "false": "taak" } },
{ "id": "taak", "block": "task.create", "label": "Ask a colleague to request the missing data",
"inputs": {
"title": "File {{trigger.record.title}} of {{trigger.customer.name}} is not complete",
"text": "Missing: {{check.ontbreekt}}",
"dueMinutes": 2880
} },
{ "id": "fout", "block": "task.create", "label": "Report that the file could not be read",
"inputs": { "title": "File {{trigger.record.title}} could not be read", "text": "{{lees.error}}" } }
]
}
Recipe 10: Reminding when the customer does not respond
When to use: a conversation has been answered, but the customer does not respond. After a few days a friendly reminder should go out.
Trigger: A conversation’s status changes (ticket.status_changed) with the condition trigger.status is Answered, and Never twice at once for the ticket (concurrencyKey: "trigger.ticketId").
Steps:
flow.waitWait 4,320 minutes (three days).flow.conditionCondition ontrigger.ticket.status. After waiting, the step reads the status as it is now.- On yes (still answered, the customer did not write back):
message.sendMessage to the customer as a proposal. - On failed:
task.createto reach the customer in another way.
Exits: true/false of the condition; sent/failed/rejected/expired of the message.
Watch out:
- On WhatsApp, a free-form message is only possible within 24 hours of the customer’s last message. After that, Message to the customer goes through failed. That is why the task is the safety net.
- Because of Never twice at once for, one reminder at a time runs per ticket. If the conversation is answered again during the wait, no new one starts; the running reminder counts from the first reply.
- If you only want to respond to replies from colleagues, add the condition
trigger.changedByUserIdis filled.
{
"trigger": {
"event": "ticket.status_changed",
"conditions": [
{ "path": "trigger.status", "op": "eq", "value": "Answered" }
]
},
"concurrencyKey": "trigger.ticketId",
"steps": [
{ "id": "wacht", "block": "flow.wait", "label": "Wait three days",
"inputs": { "minutes": 4320 },
"next": "nog-open" },
{ "id": "nog-open", "block": "flow.condition", "label": "Is the conversation still waiting for the customer?",
"inputs": { "conditions": [ { "path": "trigger.ticket.status", "op": "eq", "value": "Answered" } ] },
"exits": { "true": "herinner" } },
{ "id": "herinner", "block": "message.send", "label": "Propose reminding the customer",
"inputs": {
"message": "Hi {{trigger.ticket.customerName}}, does this answer your question? Feel free to let us know if you need anything else.",
"mode": "propose",
"deadlineMinutes": 1440
},
"exits": { "failed": "taak" } },
{ "id": "taak", "block": "task.create", "label": "Ask a colleague to reach the customer in another way",
"inputs": { "title": "Reminder could not be sent for ticket {{trigger.ticket.number}}", "text": "{{herinner.error}}" } }
]
}
Recipe 11: An error route to a colleague
When to use: in every workflow in which a step can fail while a colleague can still solve it: a connected system that does not answer, a record that cannot be read.
How: give the step If this fails (onError) with a task.create Task, and give exits such as failed the same task or a task of their own. In the task you put the reason: {{<step name>.error}}.
{ "id": "ophalen", "block": "plugin.read", "label": "Fetch the customer's details",
"inputs": { "read": "PLUGIN/LEESACTIE", "input": { "email": "{{trigger.ticket.customerEmail}}" } },
"exits": { "found": "verder", "failed": "hulp" },
"onError": "hulp" }
Watch out:
- An exit such as
failedis a normal route; If this fails catches a step that could not do its work at all. See Following runs and handling errors. - Do not put it on every step. Use it where a colleague can do something.
- In an answering workflow: at If this fails of the sorting step, set the general reply step, so that the customer still gets a reply.
Recipe 12: Forwarding to an external party
When to use: messages about one topic must go to a party outside the organisation, such as a supplier or a department with its own mailbox.
Trigger: Message read (message.understood) with the condition trigger.topicId is the number of that topic.
Steps:
ticket.forwardForward by e-mail to the address, with a Note and How set to A colleague checks and sends it (propose).ticket.labelPut on a label with a label such as “With supplier”.
Exits: none; Forward by e-mail always continues with Then. Its output status says whether it was sent, rejected or expired.
Watch out:
- Forwarding to the outside is sensitive. Let a colleague check the address and the note until the workflow has proven itself.
- The reply to the customer comes from the answering workflow. If you want to let the customer know that the message has been forwarded, give a route with its own instruction in the answering workflow.
- Forward by e-mail only works with the trigger Message read: it forwards the message on which the run started.
{
"trigger": {
"event": "message.understood",
"conditions": [ { "path": "trigger.topicId", "op": "eq", "value": 318 } ]
},
"steps": [
{ "id": "door", "block": "ticket.forward", "label": "Forward the warranty question to the supplier",
"inputs": {
"to": "garantie@leverancier.example",
"note": "Warranty question from a customer, ticket {{trigger.ticket.number}}. Please reply within two working days.",
"mode": "propose"
},
"next": "label" },
{ "id": "label", "block": "ticket.label", "label": "Put on the With supplier label",
"inputs": { "label": 105 } }
]
}
Good to know
- All JSON on this page passes aishaa’s check, but the example values (ids,
PLUGIN/..., kinds of record) must exist in the organisation. Otherwise the step fails during the run. - An AI assistant saves a recipe with
workflows_save_draftand gives the link to the editor. Publishing is always done by a person in aishaa. - You can first try out every recipe in View > Try out. There you also see the real shape of the data from a connected system.
- Recipes that send the customer something (4, 6, 10) use a proposal where possible. Only choose sending at once if the organisation trusts the text.
