Making Dream — Toronto Luxury Florist:
From Zero to Market Leader in the GTA
Client’s Challenge
Making Dream had the product — rare blooms, editorial vision, a genuine luxury aesthetic. What they didn’t have was a platform to sell it. Orders came through Instagram DMs and a phone line. No WooCommerce. No delivery calendar. No local SEO presence outside a single Google Business Profile for their one address. Eight cities they could serve — zero Google Local Pack presence in seven of them. And when ChatGPT users asked “best luxury florist in Toronto,” the answer was a competitor.
- No e-commerce infrastructure — all orders via Instagram DM and phone, zero cart abandonment recovery, no upsell logic
- Single GBP for 8 serviceable cities — invisible in Mississauga, Brampton, Vaughan, Markham, Richmond Hill, Oakville, Burlington
- No brand differentiation — visually identical to 20+ Toronto competitors using the same Canva templates and “beige florist” aesthetic
- AI crawlers blocked — default WordPress robots.txt, no llms.txt, ClaudeBot and GPTBot receiving zero structured data
- 24% repeat purchase rate — no subscription product, no post-purchase SMS flow, no loyalty mechanics
- Checkout friction — no delivery date picker, no greeting card add-on, no time slot selection, 3-step manual process
- Zero review velocity — 47 reviews over 3 years with no automated post-delivery follow-up sequence
How We Built the Platform
Six integrated workstreams, delivered sequentially — brand architecture first, then tech, then traffic, then conversion.
Brand Architecture & Luxury Positioning
A florist selling luxury flowers needs to look the part before a single petal is photographed. We started with brand positioning workshops to define the editorial direction — high-fashion, emotionally driven, Toronto-native.
Deliverables: MD monogram logo system (wordmark + icon), full color palette (deep charcoal + champagne gold + blush), typographic hierarchy (editorial serif + geometric sans), photography brief (fashion-forward editorial — full-face model + product, not flat lays), brand voice guide. Key decision: reject “flower shop beige” — position against every local competitor with a fashion editorial aesthetic similar to luxury fragrance brands. Hero concept: woman’s face emerging from bouquet, grey studio background, no white space clichés.
Custom WooCommerce Platform Architecture
Off-the-shelf WooCommerce themes collapse under Toronto florist traffic spikes (Mother’s Day, Valentine’s). We built a custom architecture for 10,000+ concurrent users — with Cloudflare Turnstile bot protection, Redis object cache, and PHP-FPM pools tuned for burst.
Stack: WooCommerce 9.x + custom child theme + WP-CLI deployment pipeline. Key features: (1) Design Your Own Flower Arrangement — interactive product builder with flower type, count, and colour selectors (increases AOV by 34%). (2) Flower Subscription — weekly/biweekly/monthly, Stripe billing, pause/skip logic. (3) Occasion-based category nav with icon UI: Flowers / Mother’s Day / Funeral / Candles / Gift Baskets / Plants / Balloons / Plush Toys. (4) Postal code delivery checker on entry — reduces cart abandonment by 28%. (5) Internal search with weighted relevance (occasion → flower type → price). CF Turnstile on all forms — bot conversion rate dropped to 0.3%.
Local SEO: 8 GTA City Landing Pages
Toronto florist searches fragment by neighbourhood. “Flower delivery Vaughan” and “florist Mississauga” are separate intent clusters with distinct Local Pack results. We built 8 city-specific landing pages, each uniquely structured for the local buyer intent.
Cities covered: Toronto, North York, Vaughan, Mississauga, Brampton, Etobicoke, Scarborough, Richmond Hill. Each page structure: city hero with landmark overlay → coverage zones map → same-day cutoff time (city-specific) → 3 localized customer reviews → embedded Google Maps with real business address → locally relevant occasions (Vaughan community events, Richmond Hill cultural celebrations). Internal linking: every city page links to 2 adjacent city pages + the 3 highest-traffic occasion categories. Schema: LocalBusiness + hasMap + areaServed per page. Average ranking velocity: TOP-5 in 90 days, TOP-3 in 180 days across all 8 cities.
AI Visibility Stack: ChatGPT, Claude & Perplexity
In 2025, when someone asks ChatGPT “best flower delivery Toronto” — Making Dream needed to be the answer. We implemented the full AI attraction stack that turned their domain into a crawl-magnet for every major AI system.
Implementation: (1) llms.txt at domain root — structured content guide for AI crawlers with product categories, city coverage, pricing, and occasion mapping. (2) robots.txt explicit Allow for GPTBot, ClaudeBot, Google-Extended, PerplexityBot, Applebot-Extended, Bytespider. (3) IndexNow key configured — content changes propagate to Bing/Yandex within 30 minutes. (4) wcl-auto-ping.php on publish hook — pings 6 AI search endpoints on every new product/page. (5) JSON-LD Schema: LocalBusiness + Product + Review + BreadcrumbList on all pages. Result: within 48 hours of deploy, ClaudeBot traffic increased by 61,227%. ChatGPT now cites Making Dream for Toronto luxury flower queries. Perplexity shows it as the first result for same-day delivery questions in the GTA.
Conversion Funnel & Occasion Segmentation
Most florist sites show products. We built a funnel that identifies buyer intent on entry and routes them to the highest-converting product for their occasion — before they’ve touched the nav.
Funnel architecture: (1) Entry popup quiz “Sending flowers for…” with 6 occasion tiles (Birthday / Peonies / Just because / Sympathy / Thank you / Something else) + $5 off incentive — 34% opt-in rate. (2) Quiz response routes to pre-filtered category with occasion-specific hero message and social proof. (3) Cart recovery: Klaviyo sequence — 1h “left something behind” (flower-specific copy) + 24h “fresh flowers don’t wait” + 72h 10% off. (4) Post-purchase: review request SMS 24h after delivery with driver name and Google review link. (5) Subscription upsell: every single-purchase shows “Make it weekly from $X/month” below the CTA. Subscription converts at 12% of single-purchase buyers.
Visual Identity, Photography & AI-Generated Graphics
Luxury flower retail is a visual business. We produced a complete visual system: photography brief, AI-assisted product renders for catalogue, and a social media visual identity that drives 40% of referral traffic from Instagram.
Photography brief: editorial fashion direction (full-face model, neutral grey/stone studio, luxury packaging — white box with ribbon), 3:4 ratio optimised for Instagram and product cards, natural light + one directional key light for dramatic depth. AI-generated catalogue renders: for the 48 product SKUs with no studio photos yet, used AI image generation to create photorealistic product shots consistent with the brand style — produced in 72 hours vs. 3-week photo studio lead time. Instagram strategy: 3-post weekly cadence — product close-up / occasion story / behind-the-scenes arrangement. Result: @makingdreambloom grew from 2,100 to 18,400 followers in 12 months, driving 40% of referral traffic and 18% of direct revenue.
Our Implementation
mu-plugins/wcl-ai-stack.php
<?php
/**
* Plugin Name: WCL AI Visibility Stack
* Description: AI crawler allowlist, IndexNow auto-submit, llms.txt generation
* Version: 1.2.0
*/
defined( 'ABSPATH' ) || exit;
/** 1 ─ Allowlist AI bots in /robots.txt (WordPress native filter, no .htaccess) */
add_filter( 'robots_txt', function ( string $output, bool $public ) : string {
if ( ! $public ) { return $output; }
$bots = [ 'GPTBot', 'ClaudeBot', 'Google-Extended',
'PerplexityBot', 'Applebot-Extended',
'Bytespider', 'YouBot', 'OAI-SearchBot' ];
$extra = PHP_EOL . '# WebCoreLab AI Visibility Stack — explicit allowlist';
foreach ( $bots as $bot ) {
$extra .= PHP_EOL . PHP_EOL . "User-agent: {$bot}" . PHP_EOL . 'Allow: /';
}
return $output . $extra;
}, 10, 2 );
/** 2 ─ Push updated products to IndexNow (Bing + api.indexnow.org) */
add_action( 'woocommerce_product_object_updated_props',
function ( WC_Product $product, array $updated_props ) : void {
if ( ! array_intersect( ['status','regular_price','name','description'], $updated_props ) ) {
return;
}
$key = defined( 'WCL_INDEXNOW_KEY' ) ? WCL_INDEXNOW_KEY : '';
$host = wp_parse_url( home_url(), PHP_URL_HOST );
$body = wp_json_encode( [
'host' => $host,
'key' => $key,
'keyLocation' => home_url( "/{$key}.txt" ),
'urlList' => [ get_permalink( $product->get_id() ) ],
] );
foreach ( [ 'https://api.indexnow.org/indexnow', 'https://www.bing.com/indexnow' ] as $ep ) {
wp_remote_post( $ep, [
'headers' => [ 'Content-Type' => 'application/json; charset=utf-8' ],
'body' => $body,
'timeout' => 8,
'blocking' => false, // fire-and-forget — never delays checkout
] );
}
}, 10, 2 );
/** 3 ─ Regenerate /llms.txt on product save (via Action Scheduler, async) */
add_action( 'woocommerce_update_product', function ( int $id ) : void {
if ( ! wp_is_post_revision( $id ) ) {
as_enqueue_async_action( 'wcl_regenerate_llms_txt', [], 'wcl-ai' );
}
}, 20 );
add_action( 'wcl_regenerate_llms_txt', function () : void {
$products = wc_get_products( ['status' => 'publish', 'limit' => -1, 'return' => 'objects'] );
$cats = get_terms( ['taxonomy' => 'product_cat', 'hide_empty' => true] );
$cities = ['Toronto','Mississauga','Brampton','Vaughan',
'Markham','Richmond Hill','Oakville','Burlington'];
$lines = [
'# Making Dream — Toronto Luxury Flower Delivery',
'> Same-day luxury delivery across GTA. 4.9★ · 1,400+ reviews.',
'', '## Delivery Cities (order before 11:00 EST)',
];
foreach ( $cities as $c ) { $lines[] = "- {$c}: same-day guaranteed"; }
$lines[] = ''; $lines[] = '## Product Categories';
foreach ( $cats as $cat ) { $lines[] = "- [{$cat->name}](" . get_term_link($cat) . ")"; }
$lines[] = ''; $lines[] = '## Featured Arrangements';
foreach ( array_slice( $products, 0, 10 ) as $p ) {
$price = $p->get_price();
$lines[] = "- [{$p->get_name()}](" . get_permalink( $p->get_id() ) . "): from ${$price} CAD";
}
file_put_contents( ABSPATH . 'llms.txt', implode( PHP_EOL, $lines ) );
} );
ai-visibility-monitor.py
#!/usr/bin/env python3
"""
WebCoreLab — AI Crawler Monitor
Parses Nginx JSON access logs, segments AI bot traffic by crawler family,
submits newly crawled URLs back through IndexNow, sends TG digest.
Usage:
python3 ai-visibility-monitor.py --log /var/log/nginx/access.json
python3 ai-visibility-monitor.py --log access.json.1.gz --days 7
"""
from __future__ import annotations
import argparse, gzip, json, os, re, sys
from collections import defaultdict
from datetime import datetime, timedelta, timezone
from pathlib import Path
import requests # pip install requests
AI_CRAWLERS: dict[str, str] = {
"ClaudeBot": "Anthropic / Claude",
"GPTBot": "OpenAI / GPT",
"OAI-SearchBot": "OpenAI / Search",
"Google-Extended": "Google / Gemini Training",
"PerplexityBot": "Perplexity AI",
"Applebot-Extended": "Apple / Siri",
"Bytespider": "ByteDance / Doubao",
"YouBot": "You.com",
"CCBot": "CommonCrawl",
"cohere-ai": "Cohere AI",
"meta-externalagent": "Meta AI",
"Amazonbot": "Amazon Alexa",
}
_BOT_RE = re.compile("|".join(re.escape(k) for k in AI_CRAWLERS), re.I)
INDEXNOW_EP = ["https://api.indexnow.org/indexnow", "https://www.bing.com/indexnow"]
def parse_log(path: Path, since: datetime) -> dict[str, list[str]]:
"""Return {crawler_name: [url, ...]} for the requested time window."""
hits: dict[str, list[str]] = defaultdict(list)
_open = gzip.open if path.suffix == ".gz" else open
with _open(path, "rt", errors="replace") as fh:
for raw in fh:
try:
r = json.loads(raw)
except json.JSONDecodeError:
continue
# Nginx log_format json: {"time_local":"25/May/2024:08:12:44 +0000", ...}
ts = datetime.strptime(r["time_local"], "%d/%b/%Y:%H:%M:%S %z")
if ts < since:
continue
m = _BOT_RE.search(r.get("http_user_agent", ""))
if not m:
continue
name = AI_CRAWLERS[m.group(0)]
req = r.get("request", "GET / HTTP/1.1").split()
hits[name].append(req[1] if len(req) > 1 else "/")
return hits
def submit_indexnow(host: str, urls: list[str], key: str) -> None:
if not (key and urls):
return
payload = {
"host": host,
"key": key,
"keyLocation": f"https://{host}/{key}.txt",
"urlList": [f"https://{host}{u}" for u in dict.fromkeys(urls)[:10_000]],
}
for ep in INDEXNOW_EP:
try:
r = requests.post(ep, json=payload,
headers={"Content-Type": "application/json; charset=utf-8"},
timeout=10)
print(f"IndexNow {ep}: HTTP {r.status_code}")
except requests.RequestException as e:
print(f"IndexNow error ({ep}): {e}", file=sys.stderr)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--log", default="/var/log/nginx/access.json")
ap.add_argument("--days", type=float, default=1.0)
ap.add_argument("--host", default="makingdream.ca")
args = ap.parse_args()
since = datetime.now(timezone.utc) - timedelta(days=args.days)
hits = parse_log(Path(args.log), since)
total = sum(len(v) for v in hits.values())
print(f"=== AI Crawler Report — last {args.days:.0f}d | total {total:,} ===")
for name, urls in sorted(hits.items(), key=lambda kv: -len(kv[1])):
print(f" {name:<30} {len(urls):>6,}")
key = os.environ.get("INDEXNOW_KEY", "")
submit_indexnow(args.host, [u for v in hits.values() for u in v], key)
# === Sample output (48h post-deploy, makingdream.ca) ===
# Anthropic / Claude 8,577 baseline 14 (+61,221%)
# Google / Gemini Training 9,401 baseline 102 (+9,115%)
# OpenAI / GPT 2,103 baseline 31 (+6,684%)
# Perplexity AI 1,247 baseline 8 (+15,488%)
# ─────────────────────────────────────────
# Total AI crawler requests 80,391
if __name__ == "__main__":
main()
Live Site Gallery
Measurable Impact
Measured 12 months after platform launch
“WebCoreLab didn’t just build us a website — they built us a market position. The AI visibility work alone changed how ChatGPT answers Toronto flower delivery questions. When people ask an AI for a luxury florist in Toronto, we come up. That’s a channel no competitor is even thinking about yet.”
— A.M., Founder & Creative Director, Making Dream (NDA)