Paid Meta — Strategy & Campaign Log
The single paid marketing channel we currently invest in. This doc holds the operational reality: account setup, audiences, current campaigns, recent history (90 days), lessons learned, and guardrails specific to Meta.
Other channels exist (organic social, Google, referrals, partnerships, in-person events, Community Manager word-of-mouth) but are not yet structured enough to document. Documenting them now would waste effort. Add them only when they mature operationally.
Why paid Meta is our primary channel right now
- Cold-prospect reach in geographic markets we can target (Salem, eventually St. Louis)
- Pixel + audience tools mature enough to build lookalikes from real customer lists
- Lead and Purchase events both supported, fitting our two-product structure (monthly tour bookings + hourly purchases)
- Budget scales smoothly from test ($25/day) to growth without operational lift
- Existing measurement plumbing (UTMs in sessionStorage, Meta Pixel, attribution captured server-side) — adequate, with known noise (see Attribution reality below)
What it isn't:
- Not a complete picture of customer acquisition. Many customers find us via word-of-mouth, search, or partner channels that don't show up in Meta attribution.
- Not a substitute for the Community Manager / tour-driven sales motion. Meta delivers leads; CMs convert them.
Account / pixel / attribution setup
| Component | Current state |
|---|---|
| Meta Pixel ID | 925160560459047 (called "dataset" in current Meta terminology). Now read from API env (META_PIXEL_ID) per PR #652. |
| Tracked events (Pixel, browser) | PageView, Purchase, Lead, CompleteRegistration, InitiateCheckout, ViewContent. All four conversion events have a matching event_id for browser/CAPI dedup. Also: LandingFunnelChoice, ScrollDepth75 (LP-specific). autoConfig is set to false — only explicit fbq("track", ...) calls fire; DOM-scan inferred events (e.g., SubscribedButtonClick) are disabled. |
| Conversions API (CAPI) | Live as of 2026-04-27 (PR #652). Server-side events for Purchase, Lead, CompleteRegistration, InitiateCheckout. Implementation in apps/api/src/lib/meta/ (capi-client.ts, hash-identity.ts, track-purchase.ts, track-lead.ts, track-registration.ts, track-checkout-init.ts). Fires fire-and-forget via after() with fetch timeout; Graph API v25. |
| Domain verification | Verified via DNS TXT record (no meta tag in layout.tsx). |
| UTM convention | ?utm_source=facebook&utm_medium=paid&utm_campaign={name}&utm_content={ad_name} |
| fbclid + UTM capture | Captured from URL on every page load (apps/web/src/components/tracking/UTMCapture.tsx). 90-day _fbc cookie written on capture; first-touch fbclid persisted on User record for cross-device delayed attribution. |
| EMQ identity payload (CAPI) | Hashed email, phone, first/last name, city, state, zip, country + plaintext client IP, user-agent, _fbp cookie, _fbc cookie, external_id (internal user ID). Target EMQ ≥ 8.0. |
| DB attribution | conversion_attributions table + AttributionService carry forward attribution from Inquiry → revenue events. syncFirstTouchAttribution helper for first-touch logic. |
| Customer audiences | Built via apps/api/scripts/export-meta-audience.ts --production (filters test accounts, outputs to gitignored .local/) |
Funnel strategy
Audience-size reality for Salem
Before choosing a funnel shape, understand the math:
- Salem city ~75k population, ~175k metro
- ~33% housing stock is apartment-type — apartment pain is broad and real, not a niche segment
- 1% LAL Salem +25mi: <1,000 people
- 5% LAL Salem +25mi: recently expanded; Meta's audience-size estimate not yet available (expected sub-tens-of-thousands at the upper bound)
- Meta's recommended floor for stable Sales-objective optimization: ~500,000+
This is why strategy is phased. Cherry City's audience is two orders of magnitude below Meta's recommended Sales-optimization floor. At sub-thousands-to-low-thousands audience, direct Sales-objective optimization on a custom mid-funnel event is theater — the ML can't find meaningful variance within the pool. The phased approach uses Phase 1 to build that pool before asking the algorithm to optimize against it.
Standard 2026 playbook (40–50% prospecting / 20–30% retargeting / 20–30% retention) — when it applies
The standard parallel hybrid funnel assumes a warm retargeting pool already exists. In an established market or a market where paid ads have run long enough to accumulate pixel data, the three-layer structure (TOFU + MOFU + BOFU) delivers better CPL than cold-only because retargeting converts at ~71% higher ROAS than cold prospecting.
That structure is the target end state, not the starting state. Don't apply it to week 0.
Phase 1 — awareness + retargeting-pool building (current for Salem)
When: Salem week 0. No warm pool exists. Launch immediately; run until Phase 2 trigger is met.
| Field | Value |
|---|---|
| Campaign objective | Traffic (NOT Sales) |
| Performance goal | Landing Page Views (NOT Link Clicks — LPV is a stronger intent signal and keeps the audience pool higher-quality) |
| Budget | $100/day, fully allocated to Phase 1 |
| Audience | Advantage+ Audience with 5% LAL Monthly Members as suggestion; age 25–45 suggestion; existing-customer Custom Audience as hard exclude. See cherry-city-ppc-prospecting/spec.md for full config + Plan B fallback. |
| Creative | Unified split-image ad — see Cherry City unified LP ad below |
| Optimization target | LandingFunnelChoice fires as MEASUREMENT only, not optimization target — accumulates retargeting custom audience |
Audience note: 2026 Meta gutted detailed interest stacking on Jan 15 2026; Advantage+ Audience is the new 2026 default for Traffic-objective campaigns at $50+/day. Cherry City's small-pool reality (≤low thousands) makes Advantage+ a particularly good fit — the AI finds within-pool variance that manual stacking cannot. Manual Page-based targeting (gear retailers like Guitar Center, Sweetwater, Reverb) survives as a Plan B fallback if Advantage+ underperforms after 14 days. See docs/features/cherry-city-ppc-prospecting/spec.md for the full audience spec.
Campaign migration mechanic: Duplicate META_CherryCity_Monthly → modify the duplicate (URL /lp/cherry-city, objective Traffic-LPV, creative new unified ad, audience expanded) → pause original, do NOT delete. Deleting loses historical data and accumulated social-proof engagement on existing winning ads.
Phase 2 — retargeting + conversion (deferred)
Trigger: "LP visitors, last 90 days" custom audience reaches ~1,500–2,000 unique users. At $100/day Phase 1 spend, estimated 4–8 weeks out.
Why this threshold: ~1,500–2,000 is the industry floor for retargeting to deliver consistent signal at $20–30 CPM. Below this, the algorithm doesn't have enough within-pool variance to optimize against.
| Field | Value |
|---|---|
| Campaign objective | Sales |
| Optimization events | Lead (monthly tour request), InitiateCheckout / Purchase (hourly) |
| Audience | Warm custom audiences only (see Custom audiences to define now below) |
| Budget split | Shift toward MOFU/BOFU as warm pool matures; cold (TOFU) can run parallel |
Do NOT launch Phase 2 early to hit a deadline. Premature Phase 2 with an insufficient warm pool results in the algorithm thrashing — it can't find signal — and wastes budget at high CPLs. If timeline pressure exists, the lever is creative refresh / audience expansion in Phase 1, not advancing to Phase 2.
Custom audiences — define now, use in Phase 2
These are zero-cost to define. Define them at Phase 1 launch so they start accumulating from day 1. Don't run ads against them yet.
| Audience | Window | Purpose |
|---|---|---|
LP visitors (/lp/salem) |
30d | Short retargeting window — recent intent |
| LP visitors | 60d | Medium window |
| LP visitors | 90d | Phase 2 trigger measurement + primary seed |
| LP 75% scroll depth | 90d | High-intent segment within LP visitors |
LandingFunnelChoice fired (any) |
90d | Clicked a fork CTA — strong intent signal |
LandingFunnelChoice fired (monthly only) |
90d | Monthly-curious sub-segment |
LandingFunnelChoice fired (hourly only) |
90d | Hourly-curious sub-segment |
| Ad engagers (saved, commented, dwelled) | 90d | Warm from ad interaction, not yet LP visitors |
Phase 1 saturation signals to monitor
If these appear, the response is creative refresh or audience expansion — NOT premature Phase 2 launch:
- Frequency >5/week → creative fatigue; refresh ad
- CTR declining consecutively over multiple days → ad fatigue
- CPM rising week-over-week → audience pool exhausting
- If saturation hits before the Phase 2 trigger threshold is met: override Advantage+ Audience to the manual Plan B fallback (5% LAL + broad interests + Page-based gear retailers) per
cherry-city-ppc-prospecting/spec.md, or refresh creative — before considering Phase 2
The three-layer TOFU/MOFU/BOFU structure (Phase 2+ / established markets)
Once the warm pool reaches Phase 2 trigger, or for markets with existing pixel history, the three-layer parallel funnel applies:
| Layer | Stage | Objective in Meta | Audience | Creative | Goal |
|---|---|---|---|---|---|
| TOFU | Top of funnel | Engagement or Video Views (NOT "Awareness") | Broad geo +25mi + Page-interest layer (instrument brands, retailers). Exclude All Customers. | Studio interior carousel or walkthrough video. Non-promotional, identity-first. | Build retargeting pool + brand recognition. |
| MOFU | Middle of funnel | Leads (or Conversions) | LP visitors (last 90 days) + ad engagers (last 30 days). Exclude All Customers + recent leads. | Strongest converting creative (e.g., Community – Just Got Louder). | Convert warm pool to tour requests / email captures. |
| BOFU | Bottom of funnel | Leads or Sales | 1% LAL – Monthly Members, geo-filtered to launch market. | Direct response creative. | Convert cold-but-similar audience. |
Budget split for new-to-warm-pool transition:
- TOFU: 25–30%
- MOFU: 25–30%
- BOFU: 40–50%
As the warm pool matures (typically 4–6 weeks after Phase 2 launch), shift budget weight toward MOFU/BOFU as retargeting CPL improves.
Hourly stays single-layer
Hourly is single-layer BOFU regardless of market phase because:
- Hourly is the new product line being proven. Restructuring its only paid channel would disrupt the proving phase.
- Hourly per-sq-ft revenue is significantly higher when filled (~$540–1,080/day potential at full utilization vs. ~$280/day for monthly). Different fill economics warrant separate strategy.
- Hourly conversion is a different ICP and funnel shape (instant online purchase vs. tour-led close). Layering TOFU/MOFU on hourly may be worth doing later but isn't the priority while monthly fill is the operational gap.
Agent guidance — funnel phase awareness
When generating ad creative, retros, or recommendations:
- Always state which phase you're in before making funnel recommendations. Phase 1 and Phase 2 have completely different objectives, audiences, and success metrics.
- Don't apply the three-layer split to week-0 markets. It assumes a warm pool that doesn't exist yet.
- Retargeting performance should be reported separately from cold prospecting. Don't average them.
- "Increase budget" recommendations must specify which phase and why. Adding BOFU spend in Phase 1 is wrong — Phase 1's only goal is pool-building.
- Don't advance to Phase 2 to hit a deadline. The trigger is a number (warm audience size), not a date.
Audience strategy
Active audiences
| Name | Type | Size | Purpose |
|---|---|---|---|
| All Customers | Custom (customer list) | ~554 emails (as of Apr) | Exclusion on all prospecting + retargeting ad sets |
| Monthly Members | Custom (customer list) | ~436 emails (as of Apr) | Lookalike seed |
| Lookalike (1%) - Monthly Members | Lookalike | <1,000 (Salem +25mi) | Phase 1 audience suggestion under Advantage+ (formerly listed as 190–224K — wrong) |
| Lookalike (5%) - Monthly Members | Lookalike | TBD — recently created, Meta estimate pending | Phase 1 audience suggestion under Advantage+; expected order of low thousands |
| Cherry City Page Visitors (90d) | Custom (website) | TBD on LP launch | MOFU retargeting input |
| Ad Engagers (30d) | Custom (engagement) | TBD on launch | MOFU retargeting input |
Audience rules
- Always exclude All Customers from any prospecting or retargeting ad set. We're not paying to re-acquire existing members.
- Lookalike seed = Monthly Members only, not all customers. Monthly is the high-LTV target; the seed should reflect that.
- Lookalike size depends on market scale, not just maturity. Under Advantage+ Audience (Phase 1), the LAL is a suggestion and the algorithm flexes around it — 5% LAL is the default starting point. For non-Advantage+ ad sets in small markets (Salem-scale: geo+age population <500K), use 5–10% LAL; a 1% LAL in Salem +25mi yields <1K matched and saturates immediately. For large markets (Portland +25mi, eventually St. Louis): 1% LAL has scale and is preferred. Use 2–3% LAL as the in-between for medium markets or when 1% has frequency >3.
- Refresh customer list audiences quarterly, or when membership churn shifts the seed by >5%. The script handles this; staleness is the failure mode.
- MOFU exclusion: exclude customers who already submitted a lead (Inquiry record exists) within the last 30 days. They're handled by the CM's tour pipeline, not by another retargeting impression.
Targeting parameters (current)
- Geography: Salem, OR + 25mi radius (Cherry City focus). Adapt per market for new launches.
- Age: 25–45 for Phase 1 (skewed toward the 30s drummer ICP). Earlier campaigns ran 18–45; tightened in May 2026 with the unified-LP campaign. Under Advantage+ Audience, age is a suggestion — Meta may flex outside if signal supports it.
- Placements: Advantage+ Placements (Meta picks Reels, Feed, Stories, etc.). Don't pre-restrict placements unless data justifies it.
- Audience targeting (Phase 1): Advantage+ Audience with 5% LAL Monthly Members as suggestion. 2026 default. See
docs/features/cherry-city-ppc-prospecting/spec.mdfor full config. - Manual Plan B fallback (Phase 1): 5% LAL Monthly Members + 3 broad music-related interests (verify in Ads Manager which still resolve: e.g., "Music," "Musical instruments," "Concerts") + Page-based interest layer (Guitar Center, Sweetwater, Reverb — gear retailers, not instrument brand Pages). Hold in reserve, don't run as parallel ad set. Override only if Advantage+ underperforms after 14 days.
- Why not manual interest stacking by default: Meta gutted detailed interest categories on Jan 15 2026 (genres, narrow musician interests, etc.). Stacking 4–6+ narrow interests now constrains Meta's algorithm rather than helping it. Even Page-based targeting alone leaves Advantage+ with broader expansion latitude — preferred at $50+/day budgets in 2026.
Current campaign structure
Two campaigns currently in market, both Cherry City, plus a pending third for the unified LP Phase 1. The sub-sections below describe the current state and what's planned.
Cherry City unified LP campaign (Phase 1 — live as of 2026-05-06)
| Field | Value |
|---|---|
| Campaign | Cherry City - Unified Prospecting (duplicate of META_CherryCity_Monthly, objective changed to Traffic) |
| Ad set | TOFU - Advantage Lookalike 5 percent |
| Ad | This sucks |
| Objective | Traffic → Landing Page Views |
| Budget | $100/day, fully allocated |
| Landing page | /lp/salem (market LP template — /lp/cherry-city 301-redirects here since PR #682) |
| Audience | Advantage+ Audience with 5% LAL Monthly Members as suggestion; age 25–45; existing-customer hard exclude. See cherry-city-ppc-prospecting/spec.md. |
| Creative | Split-image ad, four aspect-ratio variants (4:5, 1:1, 9:16, 16:9) uploaded via Flexible media. See Cherry City unified LP ad below. |
| CTA | Learn More |
| Migration | Duplicated META_CherryCity_Monthly 2026-05-06; both META_CherryCity_Monthly and META_CherryCity_Hourly paused (not deleted) at launch. |
| Status | Live as of 2026-05-06 — pending Meta review (24–48 hr typical), then delivery begins. |
Campaign 1: META_CherryCity_Monthly (paused 2026-05-06)
| Field | Value |
|---|---|
| Objective | Leads → Lead event (tour bookings) |
| Budget | $30/day (campaign-level, Advantage+ distribution). Raised from $15/day launch baseline. |
| Landing page | /cherry-city/tour → now redirects to /lp/cherry-city per PR #676 |
| CTA | Learn More |
| Launched | 2026-04-03 |
| Status | Paused 2026-05-06 when unified LP campaign launched. Not deleted — preserves history + post-engagement social proof. |
Campaign 2: META_CherryCity_Hourly (paused 2026-05-06)
| Field | Value |
|---|---|
| Objective | Sales → Purchase event |
| Budget | $20/day (campaign-level, Advantage+ distribution). Raised from $10/day launch baseline. |
| Landing page | /cherry-city/hourly → now redirects to /lp/cherry-city per PR #676 |
| CTA | Book Now |
| Launched | 2026-04-03 |
| Status | Paused 2026-05-06. Not deleted — preserves history + post-engagement social proof. |
Spend allocation — current vs. target
Current state (single-layer BOFU on both campaigns):
| Campaign | Daily | Monthly |
|---|---|---|
| Monthly Lockouts | $30 | ~$900 |
| Hourly Studios | $20 | ~$600 |
| Total | $50 | ~$1,500 |
Phase 1 actual state (Traffic/LPV, pool-building) — as of 2026-05-06 launch:
| Campaign | Daily | Monthly | Status |
|---|---|---|---|
| Unified LP Phase 1 (Traffic/LPV) | $100 | ~$3,000 | Live 2026-05-06 (Meta review 24–48 hr, then delivery) |
| Hourly BOFU (Conversion) | $0 | $0 | Paused 2026-05-06 alongside Monthly |
| Monthly BOFU (existing, paused) | $0 | $0 | Paused 2026-05-06 — preserved for social proof, not deleted |
| Total Phase 1 | $100 | ~$3,000 | Unified LP campaign replaces both Monthly + Hourly |
Phase 2 target state (once warm pool ~1,500–2,000; estimated 4–8 weeks after Phase 1 launch):
| Layer | Daily | Monthly | Status |
|---|---|---|---|
| Phase 1 (ongoing or refreshed) | TBD | TBD | May continue as TOFU |
| MOFU Retargeting (warm) | TBD | TBD | Launch at trigger |
| BOFU / Sales conversion | TBD | TBD | Launch at trigger |
| Hourly BOFU | $20 | ~$600 | Continues |
See Funnel strategy section for the phased logic and Phase 2 trigger criteria.
Currently live ads (snapshot 2026-04-26)
Three ads active across the two campaigns; three paused on 2026-04-23 after the mid-campaign review.
| Status | Ad name | Campaign | Spend (Mar 28–Apr 26) | Impr. | Reach | Results | Cost / result | Quality / engage / convert ranking |
|---|---|---|---|---|---|---|---|---|
| Active | Community – Just Got Louder | Monthly | $273.08 | 29,297 | 9,704 | 4 leads (Pixel) | $68.27 / lead | Avg / Above avg / Below avg (bottom 35%) |
| Active | Community – Just Got Louder – Copy | Monthly | $88.05 | 8,040 | 3,392 | 1 lead (Pixel) | $88.05 / lead | Avg / Above avg / Avg |
| Active | No Commitment | Hourly | $242.30 | 26,546 | 12,877 | 7 purchases (Pixel) | $34.61 / purchase | Avg / Above avg / Avg |
| Paused | Noise – Blast Beats | Monthly | $7.98 | 969 | 746 | 0 | — | — |
| Paused | Gear – Walk In | Hourly | $31.65 | 4,878 | 3,918 | 0 | — | Avg / Above avg / Above avg |
| Paused | 247 – Your Schedule | Monthly | $30.94 | 2,544 | 1,107 | 1 lead (Pixel) | $30.94 / lead | Avg / Above avg / Avg |
Total spend across the window: ~$674 over 30 days = ~$22/day average. Note: this window mostly precedes the 2026-04-23 budget raise (monthly $15 → $300/day, hourly $10 → $20/day). Going forward, weekly spend should ramp from ~$150/wk toward ~$2,250/wk as delivery catches up to the new budget. The next pull will look very different.
All numbers above are Meta-reported (unreconciled). They include cross-location view-through, credit-paid $0 purchases, and other noise per the Attribution section. DB-side conversion_attributions is the source of truth for real conversions.
Notable signals from this snapshot:
- No Commitment is the volume + efficiency winner — most results, lowest cost-per-result, on the hourly side. Confirms the hourly-ICP fit of the no-commitment angle.
- Community – Just Got Louder is the monthly volume leader but flagged "Below average (bottom 35%)" on conversion-rate ranking — Meta thinks the LP-after-click is underperforming peers. Worth investigation in retros.
- Community – Just Got Louder – Copy (a variant) costs $88/lead vs. the original's $68/lead — variant performing worse than control. Don't promote the variant.
- Blast Beats paused with negligible spend — confirms the genre-narrow lesson; never proved out.
- Gear – Walk In paused after $31 with zero results — hourly campaign ad that didn't ship enough volume to learn from before being pulled.
- 247 – Your Schedule ran briefly, generated 1 lead at $31, paused. Was a third hourly concept testing the 24/7 angle.
Cherry City unified LP ad — approved creative (2026-05-05)
Format: AI-generated split-image still, vertical 4:5 (1080×1350).
| Element | Value |
|---|---|
| Top half | Cramped working-class apartment, band rehearsing in domestic chaos. Mixed-use clutter (laundry basket, takeout containers, kids' toys). "Quiet Hours" notice on the door. Marshall stacks as incongruous pro gear. Practical daylight. Working faces, not glamour. |
| Bottom half | Same band in Cherry City studio. Acoustic treatment, mood lighting, dialed-in space. Professional. |
| Image headline | "This sucks." (top) / "This doesn't." (bottom) |
| Price line | "Go private from $285 mo or drop-in from $15 hr" (small, white, bottom of image) |
| Logo | Metrognome, bottom right |
| Primary text | "Salem bands: practicing in your apartment sucks. Cherry City built rooms for this." |
| Meta headline | "Salem rehearsal studios — from $15/hr" |
| CTA button | Learn More |
| Reference image | docs/features/unified-cherry-city-lp/This-sucks.png |
Voice principle applied: "sucks" is the audience's word — not "is a job," "isn't cutting it," or "is a struggle." The same word must carry across all elements (image headline, primary text) — voice consistency within a single ad unit. See brand-voice.md.
Creative direction: the before-state (apartment) must read as painful, not charming. Specifically avoid: scene-romanticized aesthetics (band posters as taste signal, pizza-and-beer cliché), mood lighting on the apartment side, bands shown playing quieter than full volume. The contrast is chaos → professional, not scrappy-DIY → slightly-better-DIY. Cherry City is the professional space.
Recent campaign log (last 90 days)
Past 90 days = approximately late January 2026 through April 26, 2026. Anything older isn't documented in a structured way and isn't worth reconstructing.
2026-Q1 (Jan–March): pre-Cherry-City-paid period
- Limited or no paid Meta activity in this window. Cherry City opened 2025-11-01; paid social was deferred until product/photography/landing pages were ready. Worth verifying with Aaron + Paul if any test campaigns ran in this window — current memory: no.
2026-04-03: Cherry City paid Meta launch
Two campaigns launched simultaneously per the structure above. Initial creative:
Monthly campaign — initial ads:
| Ad | Primary text | Headline | Result |
|---|---|---|---|
| Noise – Blast Beats | "Your neighbors don't appreciate your blast beats at 11pm. We do." | Be As Loud As You Want | Underperformed. Genre-narrow language → too metal/punk-specific for broader rock audience. See lessons. |
| Community – Got Louder | "Salem's music scene just got louder. Come see why." | Cherry City Studios | Higher engagement than Blast Beats; community framing landed better |
Hourly campaign — initial ads:
| Ad | Primary text | Headline | Result |
|---|---|---|---|
| Gear – Walk In | "Drums, amps, PA, mics. Just bring yourself." | Walk In. Plug In. Play. | UTM capture issues (0% on this ad — investigated, see Attribution reality — preview clicks showed UTMs preserved fine; root cause unconfirmed) |
| No Commitment | "No commitment. No contract. Just book an hour and go play." | Starts at $15/hr. That's It. | Best engagement of the four launch ads on the hourly side. The "no commitment" angle resonates for hourly drop-in customers (different ICP than monthly — see icp.md) |
2026-04 (mid-late): LP rebuild + iteration
- Cherry City tour LP rebuilt to email-only capture form (HubSpot-style) to reduce barrier to entry. Phase 1 shipped, deployed via PR.
- Carousel slideshow ads with per-card headlines published as new creative, replacing the original community ad. Goal: more variety in the same campaign without spinning up new ad sets.
- The "no commitment" hourly ad kept running; remained the strongest hourly performer.
2026-04-22: Mid-campaign ad performance review
Pulled CSV from Meta covering Mar 24 – Apr 22. Several apparent discrepancies between Meta-reported metrics and our DB were observed. The methodology of the analysis (spotting cross-location attribution, recognizing $0 credit-paid Purchase events, comparing Meta dashboard to DB conversion records) was sound, but the specific numerical conclusions ("1.5% LP→inquiry conversion," "~50% UTM capture") are based on small spend ($338 over 13 days) and short windows — too small to be statistically meaningful baselines.
Don't treat those numbers as established benchmarks. Treat them as the kind of pattern the new CAPI + DB reconciliation work (see Attribution) will let us measure properly once volume and infrastructure are both in place.
2026-04-23: Iteration after mid-campaign review
Significant edits on this date based on the review:
- Three ads paused: Noise – Blast Beats (genre-narrow underperformance confirmed), Gear – Walk In (zero results after $31 spend), 247 – Your Schedule (single lead, paused after brief test).
- Community – Just Got Louder – Copy (variant of the strongest-performing monthly ad) launched as part of the iteration.
- Budgets raised modestly from the launch baseline. Monthly $15/day → $30/day (2×); hourly $10/day → $20/day (2×). Total daily spend now $50/day (~$1,500/mo) vs. $25/day at launch.
- Carousel slideshow ads with per-card headlines remained part of the creative mix; per-headline performance not yet broken out by ad name in the data we have.
2026-04-26: Snapshot of live state
See Currently live ads table above. Three active ads (Community – Just Got Louder, Community – Copy, No Commitment), three paused. ~$674 spent across the 30-day window per Meta-reported data; ~$22/day average delivery. No Commitment is the volume + efficiency winner.
2026-04-27: Funnel strategy adopted
Three-layer funnel strategy (TOFU + MOFU + BOFU) adopted as the target end state for Salem. Context: Cherry City 2 months behind 90-day fill goal; cold-conversion-only is structurally inefficient for a new market; research-backed 2026 SMB best practice is parallel hybrid funnels.
Refinement (2026-05-05): The standard three-layer split ASSUMES a warm retargeting pool already exists. Salem is at week 0 — no warm pool. Three-layer is now documented as Phase 2+ / target state, not the immediate next step. Phase 1 (Traffic/LPV, pool-building) launches first. See Funnel strategy section for the full phased structure.
2026-05-10: Tour-scheduled attribution + enriched alerts shipped (PR #695)
- New
TOURvalue inConversionSourceTypeenum — tour reservations can now be recorded inconversion_attributions. Closes the longest-standing funnel attribution gap (tour scheduling was previously invisible). - Wiring:
ReservationCreationService.createTourReservationcallscarryForwardAttribution(tx, user.email, 'TOUR', reservation.id)after the reservation is created. Inherits attribution from the user's most-recent prior Inquiry. Staff-created tours for users without a prior Inquiry skip silently. - Enriched alert emails:
fetchSourceContextnow pulls contact name/email/phone + location for INQUIRY and TOUR sources (plus scheduled time for TOUR). Subject line prefers contact email > contact name > utm_content. Mailto/tel links on email + phone. - One backfilled tour: RSV540 (
8d66ca94...) — TOUR conversion record created post-merge byapps/api/scripts/marketing/backfill-tour-attribution.tsto match its pre-existing INQUIRY attribution (c05a5fd6...).
2026-05-08: Salem market LP + tracking infrastructure shipped
/lp/cherry-cityreplaced by/lp/salem(market-level template, PR #682). Cherry City now 301-redirects to/lp/salem. Portland and any future market get the same template for free.- Email-capture abandoner flow (PR #689): visitors who submit their email on the Salem LP but don't complete scheduling are captured via
/leads/email-captureas a separateEMAIL_CAPTUREinquiry type — ensuring follow-up can happen even for non-converts. - Meta Pixel Lead event on tour-form submit (PR
567ced695): fires immediately when the visitor submits the tour form (before the scheduler opens), with a matchingevent_idfor CAPI dedup. Measures top-of-funnel intent, not just completed tour bookings. - Pixel + CAPI dedup on tour forms (PR
f982a9cd7): browser-side Lead + server-side CAPI Lead share the sameevent_id— Meta deduplicates to one conversion per submit. - Pixel autoConfig disabled (PR
f216c9fd8):autoConfig=falsestops Meta from firing phantomSubscribedButtonClickevents on dropdown opens. Only our explicit tracking calls fire now. - Advertising opt-out compliance (PR #673): all CAPI track-* helpers gate on
isOptedOutOfAdvertising; browser Pixel gated bymg_privacy_choicescookie. GPC honored automatically./privacy-choicespage added. Privacy policy updated to v2.0.0. - PostHog replaces Umami (PR #672): product analytics now via PostHog Cloud. Session replay on all pages except
/checkout/*. Sentry events linked to PostHog session replays.
2026-05-06: Unified LP campaign launched
Phase 1 campaign Cherry City - Unified Prospecting published. Names final:
- Campaign:
Cherry City - Unified Prospecting - Ad set:
TOFU - Advantage Lookalike 5 percent - Ad:
This sucks
Configuration:
- Objective: Traffic → Landing Page Views
- Budget: $100/day fully allocated to Phase 1
- Audience: Advantage+ Audience, 5% LAL Monthly Members suggestion, Salem +25mi (people living in only — interest/near toggle off), age 25–45, exclude All Customers
- Interest suggestions added to Advantage+: Drummer (job title), Drums (instruments), Music creators (digital activities behavior), Musical instrument (instruments). Skipped Singing + Bass/Electric/Guitar specifics.
- Placements: Advantage+ Placements with all four aspect-ratio variants uploaded via Flexible media (4:5, 1:1, 9:16, 16:9 — Canva-composed)
- Tracking: Pixel
925160560459047; URL parameters use Meta dynamic variables (utm_source={{site_source_name}}+ campaign/adset/ad name macros)
Both META_CherryCity_Monthly and META_CherryCity_Hourly paused at launch — preserved for historical data + Use Existing Post engagement.
Pending Meta review (24–48 hr typical), then delivery begins. First conclusions to be drawn at day 4+ once Learning Phase early noise subsides. Phase 2 trigger: LP visitors — 90d reaches 1,500–2,000 (estimated 4–8 weeks out).
2026-05-05: Phased funnel + unified LP campaign decisions
Strategy refined and creative approved for the unified Cherry City LP Meta campaign. Spec: docs/features/cherry-city-ppc-prospecting/spec.md.
- Phase 1 launch: Traffic objective, Landing Page Views goal, $100/day fully allocated to Phase 1.
- Audience finalized as Advantage+ Audience (not manual stacking) with 5% LAL Monthly Members as suggestion, age 25–45 suggestion, existing-customer hard exclude. Validated against 2026 sources: Meta gutted detailed interest stacking on Jan 15 2026, and at $100/day Cherry City clears the $50/day Advantage+ budget threshold + the LPV-events-per-week threshold easily. Manual Plan B fallback documented in spec for use only if Advantage+ underperforms after 14 days.
- Audience-size correction: prior docs cited 1% LAL "~190–224K (Salem +25mi)" — that figure was Meta's pre-geo-filter LAL size, not the targetable pool. Actual: 1% LAL <1,000 people in geo; 5% LAL recently created, size estimate pending.
- Phase 2 deferred: trigger is warm audience (~1,500–2,000 LP visitors last 90 days), estimated 4–8 weeks out.
- Custom audiences defined now (full list in Funnel strategy section) to start accumulating from day 1. Includes new
ScrollDepth75event — engineering ask not yet in LP spec, escalate to LP owner. - Campaign name finalized:
META_CherryCity_Unified_Prospecting(formerly placeholderMETA_CherryCity_Unified_Phase1). - Creative approved: split-image still (apartment top / Cherry City studio bottom), headline "This sucks." / "This doesn't.", primary text "Salem bands: practicing in your apartment sucks. Cherry City built rooms for this.", Meta headline "Salem rehearsal studios — from $15/hr", CTA "Learn More". Reference image at
docs/features/unified-cherry-city-lp/This-sucks.png. - Campaign migration mechanic: duplicate
META_CherryCity_Monthly→ during the duplication creation flow change Objective to Traffic (Meta does not allow post-creation objective changes) → pause original Monthly + Hourly campaigns (do NOT delete — preserves social proof + data history).
Attribution
Investigation completed 2026-04-26. Implementation shipped 2026-04-27 in PR #652: feat: Meta Conversions API integration for ad attribution. All four CAPI conversion events live; cookie persistence + identity payload + dedup all wired up.
Current state in plain terms
We run browser Pixel + server-side Conversions API (CAPI) with matching event_id for deduplication. Pixel alone misses ~30–40% of conversions due to iOS 14.5+ ATT, Safari/Firefox cookie blocking, and ad blockers. CAPI bridges that gap by calling Meta directly from our backend with the same conversion event when our DB sees the conversion happen. Pixel + CAPI captures 85–95% of conversions vs. 60–70% Pixel-alone.
The four conversion events are mirrored on both surfaces:
| Event | Pixel fires from | CAPI fires from |
|---|---|---|
| Purchase | Checkout success (browser) | Stripe webhook (Payment success), waitlist deposit webhook, fulfillment paths |
| Lead | Tour form submit / scheduling / waitlist join / email-capture form | Contact form + email-capture API endpoints; trackTourFormSubmitted fires on Salem tour-form submit before the scheduler opens |
| CompleteRegistration | (paired with API) | Supabase webhook (account creation) |
| InitiateCheckout | Checkout start | Five checkout-init API routes |
Implementation lives under apps/api/src/lib/meta/: capi-client.ts, hash-identity.ts (SHA-256 per Meta's spec), track-purchase.ts, track-lead.ts, track-registration.ts, track-checkout-init.ts. CAPI calls fire-and-forget via Next.js after() with fetch timeout — they never block the user's flow even if Meta is slow.
Pixel + CAPI dedup on tour forms: The Salem market LP (/lp/salem) fires a browser-side Lead Pixel event on tour form submit, then opens the TourSchedulerModal. When the visitor completes scheduling, the server also fires a CAPI Lead event. Both carry the same event_id so Meta deduplicates — only one Lead conversion is counted per form-submit. The trackTourRequested helper in apps/web/src/lib/tracking.ts generates the event_id (passed to fbq via eventID) and returns it so the form can hand it to the server-side CAPI call.
Pixel autoConfig disabled: fbq('set', 'autoConfig', false, pixelId) is set before fbq('init', ...) in apps/web/src/app/layout.tsx. Meta's autoConfig (default on) loads fbevents-config.js which scans the DOM and fires inferred events like SubscribedButtonClick on benign interactions (e.g., opening a dropdown). Setting autoConfig=false disables this — only our explicit fbq('track', ...) calls fire.
Advertising opt-out compliance: All four CAPI track-* helpers check isOptedOutOfAdvertising via runAdTrackingAfter(req, fn) before firing. The browser Pixel is gated by apps/web/src/lib/tracking.ts reading the mg_privacy_choices cookie. Users who opt out (via /privacy-choices, GPC header, or account merge) are excluded from all Meta data flows. PostHog product analytics continues regardless of advertising opt-out.
Identity payload — why we expect EMQ ≥ 8.0
Every CAPI event carries the full identity payload Meta needs to match the conversion to a Facebook user:
- Hashed (SHA-256): email, phone, first name, last name, city, state, zip, country
- Plaintext: client IP, user-agent,
_fbpcookie,_fbccookie (or constructed from stored fbclid),external_id(internal user ID) - First-touch fbclid persisted on the User record — gives Meta a stable identity signal even when the original click happened weeks ago on a different device
Hashed email alone would score ~5–6 (under the threshold where Meta's algorithm can learn well). Full payload routinely scores 8.5+. Our customer base often has a different email on us than the one tied to their Facebook account — so the broader identity set matters more for us than average.
What changed in Meta in 2026 (still relevant for reading dashboards)
- 2026-01-12: Meta permanently removed 7-day-view and 28-day-view attribution windows. View-through is now 1-day max. Reported conversions dropped 15–40% across the industry overnight — that drop was view-through inflation going away, not real performance loss.
- March 2026: Meta redefined "click-through" to only mean link clicks. Likes, shares, saves, comments, and 5+ second video views moved to a new "engage-through" bucket with a 1-day window.
- Default attribution settings now: 7-day click + 1-day engage-through + 1-day view.
Implication: don't compare current Ads Manager numbers to pre-Jan-2026 numbers as if they're equivalent. They aren't.
Guidance for agent reports (post-CAPI)
- Meta-reported numbers are now meaningfully better but still need DB cross-checks. With CAPI live and dedup working, Meta's Purchase counts should align more closely with reality — but Meta still reports view-through, engage-through, and credit-paid $0 Purchase events as conversions. Continue labeling Meta dashboard numbers "Meta-reported" and cross-check material claims against the DB.
- For real conversion attribution, the DB remains the source of truth.
conversion_attributionsjoined to revenue tables tells us what we actually got paid for, attributed to which click. - Distinguish click-through / engage-through / view-through when interpreting Meta numbers. Don't lump them.
- Don't cite specific campaign numbers as established baselines unless they're freshly pulled and the sample is large enough to be meaningful. Earlier informal analyses (e.g., the 2026-04-17 Cherry City "1.5% LP→inquiry" / "~50% UTM capture" memos) are based on small spend and short windows; methodology useful, numbers not benchmarks.
- Verify EMQ in Events Manager. Per the brief's acceptance criteria, target EMQ ≥ 8.0 for Purchase. If we see EMQ drop below 8 over a meaningful window, flag it.
Lessons learned
Distilled from the launch period (Apr 3 onward). These are baked-in rules, not suggestions.
1. Don't go genre-narrow
The "Blast Beats" ad failed and was paused 2026-04-23 with negligible spend ($7.98) and zero conversions. Confirmed lesson: stay generalized across rock subgenres, not specific to one. Even for a city like Salem with a small scene, narrowing language to one genre's signal alienates 90% of potential rock customers. See brand-voice.md Off-voice patterns for the full rule. Agents drafting Meta ad copy must check against this.
2. Don't lead with amenities
"24/7 access," "climate controlled," "WiFi" — these are baseline expectations to our ICP, not ad hooks. Lead with displacement (apartment / garage / storage / basement) per positioning.md. Amenities show up as proof on the LP, not as the cold-traffic hook.
3. Don't say "no commitment" for monthly
Fine for hourly. Discouraged for monthly — attracts wrong-ICP customers who churn fast. The hourly No Commitment ad worked because hourly customers genuinely don't want commitment; if we ran the same hook for monthly we'd attract 30-day-and-out renters. See brand-voice.md and glossary.md.
4. "Studio" alone risks recording-studio confusion
Observed: Matador (Cherry City CM) fielded inbound contacts from people looking for recording studios when "studio" appeared in our Google ads. Same risk on Meta. Mitigations: pair "studio" with "rehearsal" / "Monthly Lockout" / "practice space" in cold-traffic copy; add negative keywords ("recording," "mixing," "mastering," "vocal booth") to paid search; consider "rehearsal space" as supplementary in metadata. See glossary.md for the full open-question framing.
5. Meta-reported metrics need DB cross-check
CAPI shipped 2026-04-27 (PR #652), so Pixel + CAPI dedup is now live and Meta's reported counts should align more closely with reality. But Meta still reports view-through, engage-through, and credit-paid $0 Purchase events as conversions. Agent retros should continue labeling Meta dashboard numbers "Meta-reported" and cross-check material claims (especially ROAS) against conversion_attributions joined to revenue tables. See Attribution above for the full picture.
6. Use Existing Post for social proof preservation
When iterating on a winning ad's creative or copy, preserve engagement by using "Use Existing Post" rather than creating a new post — this keeps reactions, comments, and shares accumulated against the same Post ID. Spinning up a new post resets all that social proof to zero.
7. The CTA matters
"Learn More" for tour-booking flows (low-commitment, lead capture). "Book Now" for hourly (transactional, immediate purchase intent). Don't mix these.
8. Layer additively, don't restructure what's working
When adding new strategy (TOFU/MOFU layers, new audiences, new ad sets), add them in parallel to working ads — don't pause, replace, or restructure ads that are converting. The cost of disrupting a working setup (lost momentum, fresh learning phase, unclear attribution) usually outweighs the cost of running an extra ad set.
9. Don't use Meta's "Awareness" objective for upper-funnel
Despite the name, Meta's "Awareness" campaign objective optimizes for low-CPM impressions and produces a low-quality retargeting pool. For TOFU work that needs to feed a useful retargeting audience, use Engagement or Video Views objectives instead. The retargeting pool from those objectives converts; the pool from "Awareness" generally doesn't.
10. Cold conversion alone is wrong for new markets
In a market with no warm audience and no Pixel pool, optimizing directly for conversion forces the algorithm to figure out the market from scratch — paying premium CPLs for the privilege. For Salem at launch: Traffic/LPV objective first (Phase 1), build the warm pool, then shift to Sales optimization (Phase 2) once ~1,500–2,000 LP visitors have accumulated. Don't advance to Phase 2 to hit a deadline — the trigger is audience size, not a calendar date.
11. Use the audience's words, not the copywriter's paraphrase
Confirmed through creative development: "practicing in your apartment sucks" beats "practicing in your apartment is a job." "Sucks" is the audience's word; "is a job" is a copywriter reaching. The blunt, unpolished word earns more trust than the clever reframe. When a word is working in one element of an ad (image headline), use the same word in all other elements (primary text, Meta headline) — not a synonym. See brand-voice.md.
12. Cherry City is professional, not scrappy
Cherry City has installed stage-style mood lighting and dialed-in acoustic treatment. Cold-traffic creative must contrast the painful before-state (domestic apartment chaos) against the professional studio — not frame Cherry City as a humble-but-lovable workaround. The creative direction is: chaos → professional. Any creative brief that describes Cherry City as "a real working room" in the sense of rough/unpolished misreads the brand. See positioning.md for the Cherry City brand position section.
13. Swap audience on a broken ad set; duplicate to test against a winning one
Audience changes always reset the learning phase. Default rule:
- Swap (in-place audience change) when the current ad set is structurally broken (e.g., audience too small to deliver, fundamentally misconfigured). There's no useful learning to preserve, so the reset is recovery, not loss.
- Duplicate (clone the ad set, change one variable in the duplicate) when the current ad set is working and you want to test a variation against it. This preserves the winning ad set's learning history while giving the variant its own learning environment. Standard pattern for iterating creative on a high-performing ad set.
Duplication doesn't avoid the learning phase — both ad sets start in learning. It only matters when there's a working ad set worth keeping running unchanged.
14. Batch all funnel changes in a single editing session
Each significant edit (audience, budget >20%, optimization event, adding/removing ads) resets the ad set's learning phase. Five small edits across five days resets learning five times; the same edits made in one session resets once. When restructuring a funnel (e.g., Phase 1 → Phase 2, adding TOFU + MOFU layers, swapping audience), do everything in one editing session, publish, then 7-day no-touch period. Don't drip-edit across days — it traps the campaign in perpetual learning.
15. Dynamic URL placeholders need double braces
Meta's dynamic URL parameters substitute at click time only when wrapped in double braces: {{ad.name}}, {{campaign.name}}, {{adset.name}}, {{site_source_name}}, {{placement}}. A single closing brace ({{ad.name} — easy to typo in the URL parameters field) passes the literal placeholder text through instead of substituting. The conversion still attributes to something (the literal string), but the data is junk — utm_content={{ad.name} instead of utm_content=community.
When configuring URL parameters on an ad, double-check the closing braces before publishing. Retros + the DB cross-check should add a guard: filter rows where utm_content LIKE '%{{%' and treat them as malformed.
Operational guardrails specific to Meta
Cross-reference guardrails.md for global agent rules. Meta-specific:
Budget controls
- Daily budget caps are sacred. Agents may operate within the approved daily budget for an active campaign. Raising a daily budget requires Aaron's approval.
- No new campaigns without approval. Spinning up a third campaign or expanding to a new market requires explicit human authorization.
- Anomaly thresholds — if daily spend deviates more than 25% from baseline, agent flags for review. If CPA degrades >50% week-over-week, agent flags for review. Agent doesn't auto-pause; flagging is enough.
Creative iteration rules
- Drafting new ad creative variants is in scope (autonomous). Publishing them isn't — propose in a draft for human approval before launching.
- A/B testing within an existing ad set (e.g., adding a third creative variant) is allowed once the variant has been approved.
- Don't kill the current control without approval. Even if a new variant outperforms, the control stays running until human says otherwise.
Audience rules
- Lookalike seed refresh can be done by the agent as a scheduled task — refreshing the existing seed list. Building a new lookalike (different seed criteria, different size, different geography) requires approval.
- Don't expand audience size or geography without approval.
Brand & compliance
- All ad copy must pass the brand-voice + glossary + guardrails checks documented across the marketing docs.
- No PII in ad creative, ever.
- Pricing claims ("from $X/mo," "from $X/hr") must match current actual lowest-priced offering. Today: $285/mo, $15/hr.
What we want to test next
Hypotheses worth running once paid-ads automation is operational. None of these are decided — flagged for agent-driven A/B once volume permits.
- Displacement target A/B — Apartment-displaced vs. Storage-unit-displaced framing on monthly cold ads. Hypothesis: storage unit is underwritten and may convert dramatically because the upgrade is so visceral.
- Lockout vs. Artist Studio for cold-prospect copy — see
glossary.mdOpen question. Both are valid; we don't know which lands better. - "Studio" + clarifier — adding "rehearsal," "Monthly Lockout," or "practice space" alongside "studio" in cold-traffic copy to mitigate the recording-studio confusion. Measure: ad bounce rate, CM-reported recording-studio inquiries.
- Hourly customer naming — if/when "drop-in" or another term lands as the canonical hourly-customer name, test it in ad copy.
- Carousel vs. single-image — observed early signal that carousel slideshow with per-card headlines performs differently than single-image. Worth proper measurement.
Open items / data gaps
- Pre-April-2026 Meta history — anything we ran before the Cherry City April launch isn't in this doc. If Aaron or Paul ran tests earlier, they aren't structured anywhere I could pull from. Acceptable gap; recent past is what's actionable.
- Real ROAS vs. Meta-reported ROAS — we don't yet have an automated reconciliation. Manual analysis on April 22 found the "6.46x" number was noise. Building a scheduled DB-vs-Meta reconciliation is a candidate first agent task.
- Search Console / Google Ads picture — not in scope here, but worth noting that paid Meta isn't isolated from search demand. As we scale Meta, we may pull more search traffic too; measuring this requires Search Console connection that doesn't exist yet.
- Negative keywords for paid search — not Meta, but related. The studio-confusion lesson means we should add recording-related negative keywords to any future Google search ads. Documented for future channel.
Related docs
docs/marketing/icp.md— who we're targetingdocs/marketing/brand-voice.md— voice rules for ad copydocs/marketing/glossary.md— vocabulary, settled vs. open termsdocs/marketing/guardrails.md— agent autonomy boundariesdocs/marketing/positioning.md— displacement frame for ad messaging