FormView on GitHub
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
Workflow nodes