Pick your service options to see an estimated price. (You can customize the pricing rules in the code.)
Fast estimate • No signup
Get a quote
This gives you a clean quote form + a pricing engine you can tweak in the PRICING object.
2) Optional: Email the quote to you (simple backend)
Static websites can’t email by themselves. Easiest: Cloudflare Workers (free tier) or Vercel. Here’s a Cloudflare Worker example.
A) Worker code (worker.js)
js
Copy code
export default {
async fetch(request, env) {
if (request.method === "OPTIONS") {
return new Response(null, {
headers: corsHeaders(request),
});
}
if (request.method !== "POST") {
return new Response("Use POST", { status: 405, headers: corsHeaders(request) });
}
const data = await request.json();
// Basic validation
if (!data?.name || !data?.phone || !data?.email || !data?.quoteTotal) {
return new Response(JSON.stringify({ ok: false, error: "Missing fields" }), {
status: 400,
headers: { "content-type": "application/json", ...corsHeaders(request) },
});
}
// Send using MailChannels (works on Cloudflare Workers without extra setup)
// Docs: https://github.com/mailchannels/mailchannels
const msg = {
personalizations: [{ to: [{ email: env.TO_EMAIL }] }],
from: { email: env.FROM_EMAIL, name: "Trash Quote Bot" },
subject: `New Trash Service Quote Request: ${data.name}`,
content: [
{
type: "text/plain",
value:
`New quote request
Name: ${data.name}
Phone: ${data.phone}
Email: ${data.email}
ZIP: ${data.zip || ""}
Details:
Customer Type: ${data.customerType}
Frequency: ${data.pickupFrequency}
Bin Size: ${data.binSize}
Bins: ${data.binsCount}
Area: ${data.serviceArea}
Extra Pickup: ${data.extraPickup}
Estimated Total: $${data.quoteTotal}
`
}
]
};
const r = await fetch("https://api.mailchannels.net/tx/v1/send", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(msg),
});
const ok = r.ok;
return new Response(JSON.stringify({ ok }), {
status: ok ? 200 : 500,
headers: { "content-type": "application/json", ...corsHeaders(request) },
});
},
};
function corsHeaders(request) {
const origin = request.headers.get("Origin") || "*";
return {
"Access-Control-Allow-Origin": origin,
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
};
}
B) Add environment variables in Cloudflare
TO_EMAIL = where you want the quote requests sent
FROM_EMAIL = a sender address you control (some setups use a domain email)
C) Front-end: send the quote to the Worker
Add this button + code inside the HTML script (I’ll keep it short). After renderQuote(q) runs, you can POST:
js
Copy code
async function sendQuote(q) {
const payload = {
name: $("name").value.trim(),
phone: $("phone").value.trim(),
email: $("email").value.trim(),
zip: $("zip").value.trim(),
customerType: q.inputs.customerType,
serviceArea: q.inputs.serviceArea,
pickupFrequency: q.inputs.pickupFrequency,
binSize: q.inputs.binSize,
binsCount: q.inputs.binsCount,
extraPickup: q.inputs.extraPickup,
quoteTotal: q.breakdown.total
};
const res = await fetch("https://YOUR-WORKER-URL.workers.dev", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(payload)
});
const out = await res.json();
if (out.ok) showMsg("Sent! We’ll contact you shortly.", true);
else showMsg("Couldn’t send right now. Please call/text us.");
}
(You’d call sendQuote(q) after calculating the quote.)
What I need from you to tailor the quote engine
If you reply with:
your prices (weekly/biweekly/monthly),
whether you charge by bin size and how much,
any service area upcharge or ZIP restrictions,
commercial pricing rules (flat rate vs multiplier),
…I’ll plug your real numbers in and also add “Call/Text” buttons + your branding (Pit Road Network style if you want).
ChatGPT can make mistakes. Check important info.