The Problem: A Black Hole of Leads and Empty Open Houses
A multi-state real estate brokerage came to us with a common, expensive problem. They were spending five figures a month on lead generation from Zillow, Realtor.com, and paid search, but their open house attendance was flatlining. Their agents, already overloaded, were manually texting new leads from their personal phones. There was no tracking, no consistency, and zero data on what was actually working.
The lead-to-attendance funnel was a sieve. A lead submitted on Tuesday for a Saturday open house might get a call on Wednesday and an email on Thursday that would sit unread. By the weekend, the lead was cold. The entire process was based on agent availability and memory, which is a faulty foundation for any scalable system. They were burning cash to acquire leads that died on the vine before ever stepping foot in a property.

Initial State Analysis: Why Email and Manual Texts Fail
We audited their existing workflow and found the core failures. Email campaigns for open houses had sub-15% open rates and click-through rates in the low single digits. For a time-sensitive event happening in 48-72 hours, email is effectively a dead channel. It’s asynchronous communication for a synchronous need.
Manual texting was slightly better for engagement but created a data black hole. We couldn’t attribute attendance to a specific lead source or agent action. We couldn’t measure response times. Worst of all, it was completely reliant on an agent remembering to follow a script, creating a chaotic brand experience and compliance risks. When an agent left, all that conversation history walked out the door with their personal phone.
The Solution: A Decoupled Architecture for SMS Logic
The goal was to intercept every new property inquiry, qualify interest in an upcoming open house, and confirm attendance via SMS automatically. We needed to do this without bogging down their existing CRM or relying on its fragile, limited native automation tools. The solution required a control plane separate from the CRM and the SMS gateway.
We architected a simple, durable system using webhooks. The CRM (in this case, Follow Up Boss) would fire a webhook on a “New Lead” event. This webhook doesn’t hit the SMS provider directly. It hits our own endpoint, a serverless function running on AWS Lambda. This middleware is the critical component. Shoving raw data from a CRM directly into an SMS API is a rookie mistake. The middleware acts as a validation and logic engine, cleaning the data and managing the communication sequence before making any API calls.
Step 1: The Webhook Trigger and Payload
Inside the CRM, we configured a webhook to fire a POST request to our Lambda’s API Gateway URL for every new lead assigned to a specific “Open House” campaign. The payload was a simple JSON object containing the lead’s name, phone number, email, and crucially, custom fields for the property address and the open house date and time.
The webhook is the starting pistol. Its reliability is paramount. We configured retry logic in the CRM, but the real resilience comes from the serverless function being able to process the request in milliseconds and return a `200 OK` status immediately. This prevents the CRM from marking the webhook as failed and shutting it down after a few timeouts, a common issue with slow or poorly designed endpoints.
Step 2: Middleware Logic and Data Sanitization
The Lambda function, written in Python, was the brain. Its first job upon receiving a payload was data sanitization. User-submitted phone numbers are a disaster zone of formats: parentheses, dashes, country codes, no country codes. We used the `phonenumbers` library to parse and normalize every number into the E.164 format required by most SMS APIs.
Any payload with an invalid number was shunted to an SQS dead-letter queue and flagged for manual review. Passing bad data downstream wastes API calls and pollutes analytics. After sanitizing the phone number, the function extracted the lead’s first name, property address, and open house details to inject into our message templates. This prevents the dreaded “Hello, null” personalization error that kills trust instantly.

Step 3: State Management and The Communication Sequence
A single SMS isn’t enough. We designed a multi-step sequence managed by the Lambda function, using a DynamoDB table to maintain state for each lead, keyed by their phone number. This setup is cheap, fast, and avoids the complexities of a traditional relational database for a simple state-tracking need.
The sequence logic was as follows:
- Initial Message (Immediate): A new lead triggers the function. It sends the first SMS: “Hi [FirstName], this is [AgentName] with [Brokerage]. Got your inquiry for [PropertyAddress]. We have an open house this Saturday at 2 PM. Can you make it? Reply Y/N.”
- Response Handling: We configured our SMS provider, Twilio, to fire a webhook back to our Lambda for all incoming replies. If the reply was ‘Y’ or ‘Yes’, DynamoDB was updated to mark the lead as ‘Attending’. If ‘N’ or ‘No’, they were marked as ‘Not Attending’, and the sequence for them was terminated.
- Reminder Message (Scheduled): A separate, scheduled Lambda function runs once an hour. It scans the DynamoDB table for leads marked ‘Attending’ whose open house is 24 hours away. It then triggers the reminder SMS: “Quick reminder about the open house tomorrow at 2 PM for [PropertyAddress]. See you there.”
- Post-Event Follow-up (Scheduled): Another scheduled function runs after the open house concludes. It queries for everyone marked ‘Attending’ and sends a follow-up: “Thanks for stopping by the open house at [PropertyAddress] today. Here’s a link to the full listing if you need it: [URL].”
This stateful, multi-touch sequence converts a one-time blast into a managed conversation. It handles confirmations, sends timely reminders, and keeps the lead warm after the event, all without an agent lifting a finger.
Step 4: The SMS Gateway – Twilio
The final step in the chain is the API call to Twilio. After all the logic checks and templating, the Lambda function makes a simple, authenticated POST request to the Twilio Messages API endpoint. We didn’t use a single phone number. We used a Twilio Messaging Service, which manages a pool of local numbers. This improves deliverability and automatically handles carrier filtering by rotating the sending numbers.
Here is a basic representation of the core API call using Python’s `requests` library. This is the final action our function takes after doing all the heavy lifting.
import os
import requests
# Environment variables hold secrets, not code.
TWILIO_ACCOUNT_SID = os.environ.get('TWILIO_ACCOUNT_SID')
TWILIO_AUTH_TOKEN = os.environ.get('TWILIO_AUTH_TOKEN')
MESSAGING_SERVICE_SID = os.environ.get('MESSAGING_SERVICE_SID')
def send_sms(recipient_number, message_body):
"""
Sends an SMS message using the Twilio API.
"""
api_url = f"https://api.twilio.com/2010-04-01/Accounts/{TWILIO_ACCOUNT_SID}/Messages.json"
payload = {
"To": recipient_number, # E.164 format
"MessagingServiceSid": MESSAGING_SERVICE_SID,
"Body": message_body
}
auth = (TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
try:
response = requests.post(api_url, data=payload, auth=auth)
response.raise_for_status() # Raise an exception for bad status codes
print(f"Message sent to {recipient_number}. SID: {response.json()['sid']}")
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error sending SMS: {e}")
# Add logging to CloudWatch here
return None
The key here is the `MessagingServiceSid`. It abstracts away the complexity of number management and compliance. Hard-coding a single `From` number is a fragile approach that will eventually get flagged by carriers.
The Results: Measurable Impact on Attendance and ROI
We ran the system for 60 days across a pilot group of 20 agents before rolling it out company-wide. The results were immediate and unambiguous. We established clear attribution by tagging every lead that entered this funnel.
Key Performance Indicators:
- Lead Response Time: Dropped from an average of 4 hours to under 30 seconds.
- Positive Reply Rate (RSVP): 38% of all new leads responded ‘Y’ or ‘Yes’ to the initial automated text. This gave agents a pre-qualified hot list to focus on.
- Open House Attendance Lift: The primary metric. We measured check-ins at the door. Automated SMS leads had a 210% higher attendance rate compared to a control group handled with the old email-only method.
- Cost Analysis: The total cost for Lambda, API Gateway, DynamoDB, and Twilio SMS for the pilot was under $100. The commission from a single extra sale resulting from this system paid for years of its operating costs.

The most valuable outcome was the data. We could finally see which lead sources generated the most engaged prospects. We discovered leads from their own website converted to an open house visit at a much higher rate than third-party portals. This allowed them to reallocate their ad spend away from low-performing channels and double down on what was actually driving foot traffic.
This wasn’t about just sending texts. It was about building a resilient, measurable system that closed a massive leak in their sales funnel. It turned a sunk cost into a predictable driver of business by forcing a timely, relevant conversation with every single lead.