I find the gaps in your process, then build the system that closes them.
Four years on the phones and in the CRM before I ever wrote a line of code. I know exactly where the bottlenecks are, because I've lived them, from the first cold call to the final closed deal.
Sales instincts, builder's habits.
I spent four years on the phones and in the CRM: cold calling motivated sellers, qualifying inbound leads, running email and SMS campaigns in GoHighLevel, and designing the property marketing that got listings noticed.
That hands-on background means I don't just build tools. I know exactly where the bottlenecks are, because I've worked through every stage myself: cold caller, lead manager, and now automation builder. I'm comfortable working independently with US-based clients, across time zones, in English, Spanish, and Arabic.
Where the phone-and-spreadsheet skills came from.
Freelance Automation & VA Projects
2025 → Present- Built and shipped multiple automation systems for real estate investors: lead enrichment, AI-powered universal data sourcing, and a live investor dashboard (see Automation Projects below)
- Work directly with US-based clients, scoping the problem, building the tool, and handing it off ready to use
Lead Manager, Marketing Specialist & VA
2023 → 2025- Ran the full lead qualification process for incoming seller and buyer inquiries, with consistent follow-up to lift conversion
- Built and executed email/SMS campaigns in GoHighLevel that grew investor engagement and listing visibility
- Wrote property marketing copy and designed listing visuals in Canva and Photoshop
- Used REISimpli and InvestorLift for lead tracking, property data, and pipeline management
- Built N8N workflows, including an AI assistant that sent emails and booked calendar meetings through Google integration
- Ran AI-assisted personal branding for the CEO's online presence
- Managed the company website via Wix, keeping listings and content current
Cold Caller & Lead Manager
2022 → 2023- Ran cold calling campaigns to find motivated sellers while managing inbound leads in parallel
- Qualified and prioritized leads so resources went to the highest-potential deals
- Kept detailed follow-up schedules that built trust across the full sales cycle
Real Estate Cold Caller
2021 → 2022- Consistently beat daily and monthly call volume and lead generation targets, building the foundation for everything since
Built to save someone hours, not to look clever.
Every project here started as a task I was doing by hand often enough that automating it paid for itself in a week.
Lead Enrichment System
Fills in the key details on every new lead: current value, expected rent, recent comparable sales, straight from a trusted, paid data provider. It scores and ranks each deal automatically so investors see the strongest opportunities first, flags duplicate leads before anyone wastes time re-researching the same property, and fires off an instant email alert the moment a strong deal is scored.
- Trigger: a Flask webhook (
/trigger) is auth'd with a shared secret header and runs the enrichment job in a background thread so the caller gets an instant response. A run-once lock and a/healthendpoint prevent overlapping runs. - Data hygiene: every read normalizes legacy/misspelled column names, drops duplicate columns, strips whitespace, and forces a fixed 26-column order, so the sheet's actual layout is never trusted blindly.
- Idempotency: rows already marked done or permanently unfixable are skipped, so completed leads are never re-processed or re-billed against the API.
- Duplicate detection: each lead is reduced to a normalized address+zip key before any API call, and a repeat is flagged and skipped, saving a wasted lookup.
- Enrichment: four RentCast endpoints (property attributes, value estimate, rent estimate, market data) are called through a shared retry wrapper with exponential backoff + jitter, retrying on timeouts/429/5xx but failing fast on real client errors.
- Deal scoring: a 100-point model weighs rent yield (30 pts), owner equity vs. last sale price (25 pts), absentee-owner status (20 pts), hold duration (15 pts), and market liquidity (10 pts), then applies a confidence penalty if the value estimate's range is unusually wide, so a shaky AVM can't score as confidently as a tight one.
- Sheet write-back: values are explicitly coerced to the right numeric/string type per column, since Sheets'
USER_ENTEREDmode needs real types to treat cells as numbers rather than text. - Notifications: an email alert fires the moment a lead is scored, so investors don't have to check the sheet manually.
AI-Powered Lead Enrichment (Universal Sourcing)
The flexible sibling of the Lead Enrichment System. RentCast is great when it has the data, but plenty of listings and county records live on sites with no API at all. This version points AI at any public URL, reads the page with Firecrawl, and pulls out whatever fields you need, giving investors coverage beyond a single paid source. Anything the AI isn't confident about gets flagged for a quick human check, so bad data never quietly slips into the sheet.
- Same webhook pattern, earlier iteration: a Flask
/triggerendpoint checks a shared secret, then hands off to a background thread: the first version of this pattern, before the run-once lock and health check were added downstream in the RentCast pipeline. - Universal sourcing: instead of a structured API, this version gives Firecrawl's AI agent a natural-language prompt plus a strict schema (property type, beds, baths, sqft, Zestimate, sale history, year built, lot size). The schema is what keeps output structured even though the source is an arbitrary webpage rather than a dedicated endpoint.
- Validation as a safety net: placeholder junk values (
N/A,-- sqft lot, etc.) are normalized to blank rather than trusted as real data. Missing property type blocks marking the row complete; a missing value estimate alone just adds a softer note. - Graceful retry: unlike the RentCast pipeline, failed or incomplete extractions aren't permanently skipped. They're automatically retried on the next run, which makes sense for an AI extraction step where a second attempt might simply succeed.
- Failure transparency: if the agent returns nothing usable, the row is marked Extraction Failed with the specific reason, instead of silently writing blank cells and marking it done.
Flip Portfolio Investor Dashboard
A live dashboard giving investors their whole flipping business at a glance: total profit, deals closed, average profit per deal, and lead-to-close conversion rate. It highlights top-performing deals, lead source breakdown, and pipeline stage counts, and tracks rehab budgets in real time so overspend gets flagged early instead of at closing. One-click export to spreadsheet or a print-ready summary, with a clear live/sample data indicator so investors always know what they're looking at.
- Live sync architecture: instead of the dashboard reading Sheets directly (which has a multi-minute publish delay), a small Flask server sits in between. A Sheet-edit trigger hits
/trigger, which re-reads the Sheet in the background, and changes appear within seconds. - Push + pull redundancy: the edit-trigger is the primary path, but the server also reads the Sheet once at boot, so the dashboard is never empty on a fresh deploy even before the first edit fires.
- Cache as a small state machine: the in-memory cache tracks not just the rows but a status (empty/ok/error), a last-synced timestamp, and the last error, so the dashboard can honestly show whether what's on screen is live, stale, or failing.
- Two rate limits, two risks: the manual refresh button is throttled globally (its cost is hitting Google's API); the polling endpoint is throttled per-IP (its risk is server load); each limited at the layer where its actual cost lives.
- Timing-safe auth: the trigger's bearer token is checked with a constant-time comparison rather than
==, closing a timing side-channel that could otherwise let an attacker infer the secret one character at a time. - Thin backend: the dashboard itself is a single static HTML file that computes every metric client-side from the raw synced rows, keeping the server a simple, replaceable sync layer rather than a data-processing bottleneck.
Neighborhood Intel
A subscription service where anyone can enter their email and any neighborhood in the world, then get a weekly AI-written report on that area: market snapshot, recent activity, local vibe, and a buyer/seller tip, delivered straight to their inbox every Monday. People can subscribe to multiple neighborhoods at once and unsubscribe anytime with one click, no login required.
- Subscribe flow: a form lets users add multiple neighborhoods as removable tags before submitting. The API validates email format server-side, caps entries at 10 neighborhoods and 100 characters each, strips unsafe characters, and is rate-limited per IP. Re-subscribing merges new neighborhoods into the existing list instead of creating duplicates.
- Unsubscribe flow: a dedicated page pre-fills the email from the emailed link so it's a one-click action, checks the record actually exists before updating it, and distinguishes "just unsubscribed" from "was already unsubscribed" as separate states.
- Newsletter generation: a cron-triggered route, protected by a bearer-token secret, pulls every active subscription and generates a Gemini-written section per neighborhood in parallel.
- Prompt-injection defense: before any neighborhood or city name is dropped into the Gemini prompt, it's sanitized to a safe character set and length, since these are public, user-supplied form values.
- XSS defense: both the AI-generated content and user-supplied neighborhood names are HTML-escaped line by line before being embedded in the outgoing email, so neither can inject markup into an email every subscriber receives.
- Per-subscriber failure isolation: the send loop wraps each subscriber in its own try/catch, so one person's failure doesn't abort the batch for everyone else; the response reports individual sent/failed statuses.
- Fail-fast config: both the Supabase and Gemini clients throw immediately if required environment variables are missing, surfacing misconfiguration at boot rather than as a cryptic runtime error mid-request.
Speed-to-Lead Open House Qualifier
A speed-to-lead tool built for the open-house moment. A visitor scans a QR code on the signage, answers three questions on their phone (timeline, pre-approval, whether they already have an agent), and gets scored Hot, Warm, or Cold instantly. The agent gets an email alert the moment a hot lead lands, so they can call back before the visitor leaves the house. NAR's own data says lead contact-ability drops ~400% after the first five minutes. The visitor-facing property interface is customizable to match the brand's identity, so every open house looks like the agent, not a template. Every sign-in logs into a password-gated dashboard backed by Google Sheets, where the agent manages listings, downloads QR codes, and sees hot/warm/cold counts per property at a glance.
- Three-question scoring: a deterministic Hot/Warm/Cold model reads just timeline, pre-approval, and agent status. A ready and pre-approved buyer with no agent is Hot; a near-term timeline with no agent is Warm; anyone already represented is capped at Cold (don't burn the first call on a long-shot nurture). No ML, no ambiguity: the agent can predict the score themselves once they know the rules.
- Instant alert, not a daily digest: the moment a lead is captured, an email alert goes out over Resend SMTP with the visitor's name, phone, the listing, the temperature, and a one-line summary like
🔥 Hot lead: Sarah, pre-approved, no agent, timeline: ASAP. Speed-to-lead is the entire product, so the alert latency is seconds, not a cron batch. - QR at the door: each listing gets a server-generated QR code (
/api/qr/[id]) encoding the property-branded sign-in URL. The base URL is taken from a single trusted env var, never reconstructed from theHostorx-forwarded-hostheaders, which closes an open-redirect-adjacent path where a spoofed header could make the QR encode an attacker URL. - Single-password dashboard auth: one shared password gates the agent dashboard. The session cookie is a signed HMAC-SHA256 token derived through HKDF, not the password itself, so a leaked cookie can't be reversed into the password. The cookie uses the
__Host-prefix (forces Secure, Path=/, no Domain), and the login comparison runs in constant time to avoid a timing oracle that could infer the password one character at a time. - Google Sheets as the database: two tabs,
Speed_To_Lead(the leads) andListings(the properties), with a 60-second read cache. The agent sees leads in a Sheet they already own and can export, no separate database to provision. Lead counts are aggregated per listing by grouping Sheet rows by address. - Bounded rate limiting: the login and lead-capture endpoints are throttled per-IP, and the in-memory limiter is capped at 10,000 buckets with eviction. Without the cap, an attacker spoofing
x-forwarded-forcould grow the Map unbounded and memory-DoS the instance, a real risk on a serverless deploy. - Property-branded sign-in page: the visitor's form page fetches the listing's address and price from a public, read-only endpoint and shows them as the masthead, so a visitor who scans the QR immediately confirms "yes, I'm at the right open house" before entering their details. No PII is exposed: the address is already on the signage and encoded in the QR.
- Editorial-luxury UI: rather than the default gray SaaS dashboard look, the interface uses a Fraunces display serif, a warm paper-and-clay palette, and a magazine-cover hot-leads hero (the one number an agent actually cares about, given real weight). Built to look like a tool an agent would remember, not a template.
At the open house, the visitor scans the QR and lands here, the property's address as masthead, three questions, done in under a minute
What's actually in the stack.
Let's build something that saves you time.
Open to remote roles in real estate operations, lead management, or automation, and open to freelance builds.






















