functional-spec.md
1 --- 2 title: 'Functional Spec' 3 category: 'architecture' 4 last_verified: '2026-03-13' 5 tags: ['functional', 'spec', 'testing', 'api', 'ai', 'llm', 'email', 'sms'] 6 status: 'historical' 7 --- 8 9 > **Historical document**: This is the original POC/MVP design spec. For the current production architecture (3-stage scoring, Claude Max orchestrator, circuit breakers, etc.), see [architecture.md](architecture.md). 10 11 # The Process 12 13 - search "\[a type of small business\] \[city or metropolitan region\]" 14 - on the search engine that's least likely to complain about automation 15 - perhaps a local business directory like Thumbtack, Angi, Yelp, Houzz (or equivalent for the relevant country) 16 - ideally a search engine / directory that includes contact details 17 - extract URL, plus contact details if included in SERP 18 - visit each website on the first SERP 19 - capture screenshots 20 - desktop above-the-fold screenshot 21 - desktop first-below-the-fold (next page down) screenshot 22 - mobile above-the-fold screenshot (375x667px) 23 - downsize screenshots 24 - resize all screenshots down to 768x432px (this is still enough for text recognition) 25 - intelligently crop out navbars, margins, redundant whitespace and non-content elements 26 - convert all images to JPEG with quality 85 27 - See [https://www.buildwithmatija.com/blog/reduce-image-sizes-ai-processing-costs](https://www.buildwithmatija.com/blog/reduce-image-sizes-ai-processing-costs) 28 - store the following: 29 - domain 30 - landing page URL (probably the same, but you never know) 31 - search keyword 32 - desktop above-the-fold screenshot 33 - desktop first-below-the-fold screenshot 34 - mobile above-the-fold screenshot 35 - a copy of the HTML DOM after pageload 36 - call LLM with the above (except the below-the-fold image) asking for a conversion score, using the \[Conversion Scoring Prompt.md\](Conversion Scoring Prompt.md) 37 - store the full JSON of the scoring result 38 - if score is \<= B-, call the LLM with the \[Conversion Scoring Resubmit Prompt.md\](Conversion Scoring Resubmit Prompt.md), with: 39 - the previous scoring result (the full JSON) 40 - the first-below-the-fold screenshot 41 - store the full JSON of the scoring result (overwrite previous) 42 - except move the new contact details section to it's own field in the table 43 - after all websites on the SERP for a keyword have been processed: 44 - search the stored data to identify the highest scoring site for that keyword 45 - record this as the primary competitor for each low-scoring site 46 - for any low-scoring sites: 47 - call LLM to generate 3 variants of the proposal text, along these lines: 48 Hi \[firstname, if found, or "Hi there" / "Hi, my name is \[sender name\]" if not\]. 49 I was looking at your website earlier and noticed your homepage offer score is about \[score\] (don’t worry — most local businesses are around that range, although \[primary competitor, with URL\] scored \[score\]). 50 Nothing is “broken,” but you’re likely missing out on at least 20–40% more leads just from the way your offer and call-to-action are structured above the fold. 51 I do Local Offer Fix Audits that break this down and show exactly what to change for fast lead increases. 52 It’s a simple one-time service — takes me about 24 hours — and the audit is yours to keep. 53 Want me to send over what I found? 54 \[sender name\] 55 - it should also generate 3 variants of the subject line 56 - include in the prompt: 57 - name and URL of the primary competitor 58 - conversion score for site and competitor 59 - samples of proposals (and their subject lines) that resulted in sales 60 - up to 5 sample pairs for this keyword 61 - up to 5 sample pairs for this country 62 - Send each version of the proposal via a different contact method gathered above, in the following order of preference: 63 - SMS if we have their mobile number, via API 64 - we need an SMS API gateway that operates in multiple countries 65 - contact form 66 - in-browser automation (not headless) 67 - best-guess prefill, pausing until operator submits form 68 - sender name, email, phone, and company will need to be stored globally for this 69 - WhatsApp 70 - This can't use the Whatsapp Business API because it doesn't work for cold messaging. It requires customer opt-in. 71 - There is a black-hat alternative using browser automation but it is more effort than it's worth 72 - If we're planning to WhatsApp them, we've got their mobile number so it's probably easier to just SMS them. 73 - So for now, we ignore WhatsApp for cold outreach 74 - cold email 75 - use a cold-email service API 76 - hybrid plaintext with html just for images/buttons 77 - use base64 encoding for images 78 - unsubscribe link 79 - maybe just avoid EU countries (GDPR requires opt-in) 80 - include a tracking pixel (for analytics, not open rate) 81 - append a globally-stored signature block to emails 82 - LinkedIn (in-browser automation) 83 - Facebook (in-browser automation) 84 - Instagram (in-browser automation) 85 - If we don't have at least 2 primary contact methods (email, contact form, phone), then open their homepage in a new tab, with a floating message telling the operator we need help to find their contact methods, and floating buttons to copy the text for each field 86 - Store each outreach, including: 87 - timestamp 88 - proposal text 89 - email subject line 90 - contact method (one of: SMS, Email, Contact Form, Whatsapp, LinkedIn, Facebook, Instagram - normalised in this way for later use as an analytics dimension) 91 - contact URI (email address, tel: phone number or URL of the form) 92 - (there will also be a boolean field to later mark that this converted into a sale) 93 - Consider bot detection issues around browser automation 94 - Hence the benefit of using a real browser 95 - Leave room for human intervention 96 - Pause the script for human review/intervention 97 - Add floating buttons to copy the text for each form field 98 - Meanwhile, continue the next step in a new browser tab 99 - Stop adding new tabs when the browser starts unloading old tabs 100 - Randomise timings and mouse movement 101 - Consider realistic mouse clicks not just calling btn.click() events 102 - I am familiar with developing in client-side vanilla JS / jQuery, so Tampermonkey might make sense here, but I'd like to be able to use VSCode's Cline to automatically test and fix errors as they appear in the browser console 103 - Provide a method for the operator to review and update the stored data 104 - Initially, just so they can flag which outreach turned into a sale 105 - Later Versions Will Add: 106 - Handle incoming responses from prospects 107 - Store against outreaches 108 - First map the email sender's domain or phone number 109 - If no match, ask LLM to extract the domain from response their message 110 - If still not sure, ask them 111 - _(also store timestamp of each response, to later identify best time of day)_ 112 - Sample initial response: 113 Awesome — here’s the deal: 114 I offer a _Local Offer Fix Audit_ that breaks down everything: 115 ✔ Your current “above the fold” offer 116 ✔ Why it's not converting 117 ✔ A competitor comparison 118 ✔ A completely rebuilt offer (written for you) 119 ✔ A new CTA 120 ✔ A 72-hour fix plan 121 It’s a _one-time $300_ service, and you get the full PDF to keep. 122 If you want, I can start your audit today. 123 Want the link to get started? 124 - Objection handling 125 - Feedback Loop 126 - _(To track what works, for continual improvement)_ 127 - Add tracking to the payment link 128 - Analytics pixel to confirm read 129 - URL shortener with tracking 130 - _(Record timestamp for each, to identify when they check their messages)_ 131 - Split Testing 132 - Add above-the-fold screenshot in first email? 133 - As well as their best competitor's screenshot? 134 - Call out specific fails in the first email? 135 - Add a cropped screenshot of the failing component? 136 - Send at different times of day (only works once fully automated)