CAC-OPS-001 · Version 1.0 · May 2026
Worker backend is frontend-agnostic. Any frontend (Eleventy, Astro, plain HTML) that POSTs JSON with course_id, event_slug, form_type, hrdc_option + form fields to the worker route will work.
URL (Eleventy page)
→ form CONFIG object (course_id, event_slug)
→ POST to worker
→ worker reads KV using those values
→ renders PDFs → sends email
The worker never knows about URLs. It only knows course_id and event_slug.
What this is: A training provider gets their own subdomain e.g. newtp.claritysystems.work with their logo and branding on all documents.
Steps:
DNS — Cloudflare dashboard → DNS → Add record
CNAMEnewtptrainingbiz-admin-clerk.clarityawarenesscoaching.workers.devWorker route — Workers & Pages → trainingbiz-admin-clerk → Settings → Domains & Routes → + Add
newtp.claritysystems.work/*KV — domain entry
Key: domain:newtp.claritysystems.work
{
"tp_key": "newtp",
"payment_paths": ["grant"],
"notify_telegram_chat_id": "THEIR_CHAT_ID"
}
KV — TP entry
Key: tp:newtp
{
"name": "TP Legal Name Sdn Bhd",
"company_id": "MYCOID_NUMBER",
"address": "Full address",
"contact_name": "Contact Person Name",
"contact_email": "[email protected]",
"contact_phone": "01X-XXX XXXX",
"bank_name": "Bank Name",
"bank_account_name": "Account Name",
"bank_account_number": "XXXX XXXX XXXX",
"sst_number": "SST-NUMBER",
"logo_url": "https://pub-XXXX.r2.dev/logo-newtp.png",
"notify_telegram_chat_id": "CHAT_ID"
}
R2 — Upload TP logo to cac-assets bucket
logo_url aboveEleventy form page — Copy /resellers/newrise/sales-training/enquiry/index.html
CONFIG.course_id and CONFIG.event_slugaction URL to newtp.claritysystems.work/sales-training/enquiryTest: Submit form on new subdomain. Check email for PDFs with TP logo.
What this is: A new training programme with its own schedule, pricing, and event dates.
Steps:
KV — course entry
Key: course:{course_id} (e.g. course:communications-101)
{
"title": "Full Course Title",
"duration_label": "2 Days",
"currency": "MYR",
"programme_desc": "One paragraph description.",
"hrdc_programme_number": null,
"corporate": {
"base_price": 8000,
"direct_discount_pct": 10,
"grant_surcharge_pct": 10
},
"public": {
"rate_per_seat": 1500,
"stripe_link": "https://buy.stripe.com/XXXX"
},
"pax_min": 15,
"pax_max": 30,
"includes_note": "Materials, certificate, meals.",
"logistics_note": "Projector, whiteboard, WiFi.",
"hrdc_block": "<p>HRDC claimable under PROLUS.</p>",
"terms": {
"corporate_direct": "50% deposit on confirmation.",
"corporate_grant": "Full payment before training date.",
"public_grant": "Full payment before training date."
},
"email_copy": {
"corporate_grant": {
"subject": "Your Training Proposal — ",
"body_intro": "Please find attached your proposal documents."
},
"corporate_direct": {
"subject": "Your Training Proposal — ",
"body_intro": "Please find attached your proposal documents."
}
}
}
KV — schedule entry
Key: schedule:{course_id}
{
"day1_title": "Day 1 Theme",
"day2_title": "Day 2 Theme",
"day1": [
{ "time": "09:00 – 09:15", "topic": "Topic name" },
{ "time": "09:15 – 10:30", "topic": "Topic name" },
{ "time": "10:30 – 10:45", "topic": "Morning Break" },
{ "time": "10:45 – 13:00", "topic": "Topic name" },
{ "time": "13:00 – 14:00", "topic": "Lunch Break" },
{ "time": "14:00 – 15:30", "topic": "Topic name" },
{ "time": "15:30 – 15:45", "topic": "Afternoon Break" },
{ "time": "15:45 – 16:45", "topic": "Topic name" },
{ "time": "16:45 – 17:00", "topic": "Topic name" }
],
"day2": [
{ "time": "09:00 – 09:15", "topic": "Topic name" },
{ "time": "09:15 – 10:30", "topic": "Topic name" },
{ "time": "10:30 – 10:45", "topic": "Morning Break" },
{ "time": "10:45 – 13:00", "topic": "Topic name" },
{ "time": "13:00 – 14:00", "topic": "Lunch Break" },
{ "time": "14:00 – 15:30", "topic": "Topic name" },
{ "time": "15:30 – 15:45", "topic": "Afternoon Break" },
{ "time": "15:45 – 16:30", "topic": "Topic name" },
{ "time": "16:45 – 17:00", "topic": "Topic name" }
]
}
KV — event entry
Key: event:{course_id}:{slug} (e.g. event:communications-101:kl-jun-2026)
{
"date_day1": "Tuesday, 10 June 2026",
"date_day2": "Wednesday, 11 June 2026",
"time": "09:00 – 17:00",
"tp_key": "newrise"
}
Eleventy form page — Copy /sales-training/enquiry/index.html
CONFIG:course_id: "communications-101",
event_slug: "kl-jun-2026",
/communications-101/enquiry/index.htmlTrainer PDF — Upload to R2 as trainer_{trainer_key}.pdf if required. Add trainer_key to event KV entry.
Test: curl POST with new course_id and event_slug. Check email.
What this is: A fully separate domain (e.g. training.newtp.com) with its own Eleventy site and visual identity, but sharing the same worker backend.
Steps:
DNS — Point training.newtp.com to Cloudflare (add site to Cloudflare or use CNAME to worker)
Worker route — + Add route: training.newtp.com/*
KV entries — Same as Step 01 (domain + tp entries) but with key domain:training.newtp.com
New Eleventy project — Copy the existing site structure
action URLs to point to worker routeCONFIG.course_id and CONFIG.event_slug per pageR2 — Upload TP logo. The same cac-assets bucket is shared — just use a unique filename.
Note: The worker is shared. The frontend is separate. PDFs will use the TP logo from tp:{id}.logo_url. No worker code change required.
Every enquiry form page must have this at the top of its script:
const CONFIG = {
course_id: "your-course-id",
event_slug: "your-event-slug"
};
These two values are the only connection between the URL and the worker data.
| What changed | How to deploy |
|---|---|
| KV data | Cloudflare dashboard → KV → edit. Instant. |
| R2 file (template, logo, PDF) | Cloudflare dashboard → R2 → upload. Instant. |
| Worker route | Dashboard → Worker → Domains & Routes → + Add. Instant. |
Worker code (index.js) |
git push → GitHub Actions auto-deploys. ~2 min. |
| Eleventy page / form | git push → GitHub Actions auto-deploys. ~2 min. |
Never: Use dashboard editor for index.js. Use wrangler locally. Direct curl deploy.