Lead Intake
Route a qualified agency enquiry
Give each incoming agency enquiry a clear next owner before a salesperson opens it. This workflow checks supplied service and budget facts, normalizes the email and returns a sales or review decision.
Experimental native example · local preview · revision 1
WORKFLOW PREVIEW
See how it works
Use + and − to zoom, F to fit. Press Enter on a node to inspect it.
Private preview. These tested drafts are available for inspection. Adding them to a Vora workspace is still being tested.
What you need
- An operator-approved service allowlist and minimum monthly budget.
- A manually supplied enquiry ID, contact email, requested service and budget; permission to use those facts in the chosen workspace.
Follow the workflow step by step
Supplied agency data
Receive the explicitly supplied manual input packet.
- default: Route a qualified agency enquiry
Route a qualified agency enquiry
Return a validated enquiry decision with a sales or review queue, reason codes and a normalized email. Leave CRM creation to an explicitly connected downstream workflow.
Setup and review
- Enter the enquiry facts and the agency qualification policy in the input JSON.
- Run a controlled manual test with a synthetic enquiry first. A budget exactly at the minimum qualifies.
- Inspect queue and reasons, then decide how to assign the enquiry. Any CRM creation needs a separately tested action.
Inspect every node
Expand a node to inspect its complete settings. Review the runtime and adoption evidence before installing it.
Supplied agency data · wfManualTrigger
{
"label": "Supplied agency data"
}Route a qualified agency enquiry · code
{
"label": "Route a qualified agency enquiry",
"code": "\nfunction obj(value, keys, required = keys) {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Expected object');\n if (Object.keys(value).some(key => !keys.includes(key))) throw new Error('Unknown input field');\n if (required.some(key => !Object.hasOwn(value, key))) throw new Error('Missing required input field');\n return value;\n}\nfunction text(value, name, max = 200, allowEmpty = false) {\n if (typeof value !== 'string' || value.length > max || (!allowEmpty && !value.trim())) throw new Error('Invalid ' + name);\n return value.trim();\n}\nfunction list(value, name, max = 500) {\n if (!Array.isArray(value) || value.length > max) throw new Error('Invalid ' + name);\n return value;\n}\nfunction int(value, name, min = 0, max = Number.MAX_SAFE_INTEGER) {\n if (!Number.isSafeInteger(value) || value < min || value > max) throw new Error('Invalid ' + name);\n return value;\n}\nfunction amount(value, name) {\n if (!Number.isFinite(value) || value < 0 || value > 1000000000) throw new Error('Invalid ' + name);\n return value;\n}\nfunction email(value) { return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value); }\nfunction day(value) {\n if (typeof value !== 'string' || !/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) throw new Error('Invalid calendar date');\n const ms = Date.parse(value + 'T00:00:00Z');\n if (!Number.isFinite(ms) || new Date(ms).toISOString().slice(0, 10) !== value) throw new Error('Invalid calendar date');\n return ms;\n}\nfunction instant(value) {\n if (typeof value !== 'string' || !/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?Z$/.test(value)) throw new Error('Invalid UTC timestamp');\n const ms = Date.parse(value);\n const normalized = value.includes('.') ? value : value.replace('Z', '.000Z');\n if (!Number.isFinite(ms) || new Date(ms).toISOString() !== normalized) throw new Error('Invalid UTC timestamp');\n return ms;\n}\nfunction uniqueIds(items) {\n const ids = items.map(item => text(item.id, 'item ID', 120));\n if (new Set(ids).size !== ids.length) throw new Error('Duplicate item ID');\n return ids;\n}\nfunction ratioBps(numerator, denominator) {\n const n = BigInt(numerator) * 10000n;\n const d = BigInt(denominator);\n const quotient = n / d;\n const floor = n < 0n && n % d !== 0n ? quotient - 1n : quotient;\n const result = Number(floor);\n if (!Number.isSafeInteger(result)) throw new Error('Unsafe calculated ratio');\n return result;\n}\n\n\nconst x = obj(context.input, ['enquiryId','email','requestedService','monthlyBudget','allowedServices','minimumMonthlyBudget']);\nconst enquiryId = text(x.enquiryId, 'enquiry ID', 120);\nconst address = text(x.email, 'email', 320).toLowerCase();\nif (!email(address)) throw new Error('Invalid email');\nconst service = text(x.requestedService, 'service', 120).toLowerCase();\nconst allowed = list(x.allowedServices, 'service allowlist', 50).map(value => text(value, 'allowed service', 120).toLowerCase());\nif (!allowed.length || new Set(allowed).size !== allowed.length) throw new Error('Invalid service allowlist');\nconst budget = amount(x.monthlyBudget, 'monthly budget');\nconst minimum = amount(x.minimumMonthlyBudget, 'minimum budget');\nconst reasons = [];\nif (!allowed.includes(service)) reasons.push('service_not_allowlisted');\nif (budget < minimum) reasons.push('budget_below_minimum');\nreturn {enquiryId, email:address, queue:reasons.length ? 'review' : 'sales', reasons};\n",
"outputVar": "result",
"timeout": 1000,
"memoryLimit": 16
}Input and output contracts
Input
{
"type": "object",
"properties": {
"enquiryId": {
"type": "string",
"maxLength": 120
},
"email": {
"type": "string",
"maxLength": 320
},
"requestedService": {
"type": "string",
"maxLength": 120
},
"monthlyBudget": {
"type": "number",
"minimum": 0,
"maximum": 1000000000
},
"allowedServices": {
"type": "array",
"items": {
"type": "string",
"maxLength": 120
},
"maxItems": 50
},
"minimumMonthlyBudget": {
"type": "number",
"minimum": 0,
"maximum": 1000000000
}
},
"required": [
"enquiryId",
"email",
"requestedService",
"monthlyBudget",
"allowedServices",
"minimumMonthlyBudget"
],
"additionalProperties": false
}Output
{
"type": "object",
"properties": {
"enquiryId": {
"type": "string",
"maxLength": 120
},
"email": {
"type": "string",
"maxLength": 320
},
"queue": {
"type": "string",
"maxLength": 20
},
"reasons": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"maxItems": 2
}
},
"required": [
"enquiryId",
"email",
"queue",
"reasons"
],
"additionalProperties": false
}Choose an output for this job
This business workflow also has 15 delivery formats. Compare their inputs, output examples and limits.
Example input and result
These examples use synthetic data.
Input
{
"enquiryId": "enq-01",
"email": " Buyer@Example.test ",
"requestedService": "automation",
"monthlyBudget": 800,
"allowedServices": [
"automation",
"support"
],
"minimumMonthlyBudget": 500
}Expected result
{
"enquiryId": "enq-01",
"email": "buyer@example.test",
"queue": "sales",
"reasons": []
}Success means: Return a validated enquiry decision with a sales or review queue, reason codes and a normalized email. Leave CRM creation to an explicitly connected downstream workflow.
When something goes wrong
- A malformed email or missing budget stops the result. Fix the supplied fact and repeat the test. A valid but unknown service or smaller budget goes to review; keep that enquiry visible rather than dropping it.
Limits and costs
- Budget is a supplied number in one agreed currency/period. The workflow does not infer purchasing intent, verify an email inbox, create a sales opportunity or assign a real CRM owner.
- No paid model calls or metered third-party operations are declared in this pure graph. Vora plan availability, compute and quotas still apply; pricing has not been verified by this batch.