Client Quote Generator
Drafts a quote from your rate card, holds it for human approval, then sends it from your own mailbox and logs it to a stream.
aiintegrationcode
Source
/**
* Client quote generator. A quote request comes in from a public form,
* AI drafts the quote from your rate card, a human approves it, and the
* quote goes out from your own Microsoft 365 mailbox. Every quote is
* persisted to a stream for reporting.
*
* Setup:
* 1. swirls add microsoft send_mail
* 2. Deploy, then authorize the Microsoft connection in Cloud -> Connections.
* 3. Edit the rate card in the draft_quote prompt to match your pricing.
*/
connection client_microsoft {
label: "Client Microsoft 365"
provider: microsoft
}
schema quote_request_payload {
label: "Quote request"
schema: @json {
{
"type": "object",
"required": ["contact_name", "contact_email", "company", "project_description"],
"properties": {
"contact_name": { "type": "string", "title": "Your name" },
"contact_email": { "type": "string", "format": "email", "title": "Email" },
"company": { "type": "string", "title": "Company" },
"project_description": { "type": "string", "title": "What do you need?" }
}
}
}
}
form quote_request {
label: "Request a Quote"
visibility: public
schema: quote_request_payload
}
workflow generate_quote {
label: "Generate Quote"
description: "Draft a quote from the rate card, hold for human approval, then email it to the client."
root {
type: code
label: "Receive request"
inputSchema: quote_request_payload
code: @ts { return context.nodes.root.input }
outputSchema: quote_request_payload
}
node draft_quote {
type: ai
label: "Draft quote"
kind: object
model: "google/gemini-2.5-flash"
prompt: @ts {
const req = context.nodes.root.output
return "Draft a service quote.\n\nRate card:\n- Discovery and scoping: 950 flat\n- Workflow build: 1800 per workflow\n- Integration setup: 600 per system connected\n- Monthly run and support: 400 per month\n\nRequest from " + req.contact_name + " at " + req.company + ":\n" + req.project_description + "\n\nPick only the line items the request needs, compute the total, and write a short professional email presenting the quote. Address the client by first name. Do not invent services that are not on the rate card."
}
schema: @json {
{
"type": "object",
"required": ["line_items", "total", "email_subject", "email_body"],
"properties": {
"line_items": {
"type": "array",
"items": {
"type": "object",
"required": ["description", "amount"],
"properties": {
"description": { "type": "string" },
"amount": { "type": "number" }
}
}
},
"total": { "type": "number" },
"email_subject": { "type": "string" },
"email_body": { "type": "string" }
}
}
}
}
node approve_quote {
type: code
label: "Approve quote"
code: @ts { return {} }
review: {
enabled: true
title: "Approve this quote"
description: "The drafted quote is in the draft_quote node output. Approve to send it to the client, or reject to stop the run."
schema: @json {
{
"type": "object",
"properties": {
"notes": { "type": "string", "title": "Internal notes" }
},
"additionalProperties": false
}
}
actions: [
{ id: "send", label: "Send quote", outcome: "approve" },
{ id: "reject", label: "Reject", outcome: "reject" }
]
}
}
node send_quote {
type: integration
label: "Send quote"
connection: client_microsoft
action: microsoft_send_mail
params: @ts {
const req = context.nodes.root.output
const quote = context.nodes.draft_quote.output
return {
message: {
subject: quote.email_subject,
body: { contentType: "text", content: quote.email_body },
toRecipients: [
{ emailAddress: { address: req.contact_email, name: req.contact_name } }
]
},
saveToSentItems: true
}
}
}
node record_quote {
type: code
label: "Record quote"
code: @ts {
const req = context.nodes.root.output
const quote = context.nodes.draft_quote.output
return {
company: req.company,
contact_email: req.contact_email,
total: quote.total,
sent_at: new Date().toISOString()
}
}
}
flow {
root -> draft_quote
draft_quote -> approve_quote
approve_quote -> send_quote
send_quote -> record_quote
}
}
stream sent_quotes {
label: "Sent quotes"
workflow: generate_quote
version: v1
versions: {
v1 {
schema: @json {
{
"type": "object",
"properties": {
"company": { "type": "string" },
"contact_email": { "type": "string" },
"total": { "type": "number" },
"sent_at": { "type": "string" }
}
}
}
prepare: @ts {
return context.output.record_quote
}
}
}
}
trigger on_quote_request {
form:quote_request -> generate_quote
enabled: true
}
Flow
Trigger to workflow
quote_requestform
generate_quote
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.
Workflow nodes
CODE
Code Node
Receive request
AI
AI Node
Draft quote
CODE
Code Node
Approve quote
INTEGRATION
Integration Node
Send quote
CODE
Code Node
Record quote
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.