Parsing Property Portal Lead Emails Into Clean Records Before Any AI Touches Them
Many property portals still deliver leads to small agencies as notification emails. Before you add an AI assistant to the follow-up, turn those emails into clean, structured records. Clean input makes everything downstream simpler, from dedupe to follow-up timers.
This intake step is the unglamorous start of real estate lead automation for small agencies, and it is worth getting right.
The shape you want
{"source": "portal_a", "received_at": "2026-10-19T09:12:00+05:30", "name": "...", "phone": "...", "email": "...", "listing_ref": "...", "message": "...", "raw_id": "<message-id>"} One parser per sender
Each portal formats its emails differently, so write one small parser per sender address and route by the From header.
import re def parse_portal_a(body: str) -> dict: def grab(pattern): m = re.search(pattern, body) return m.group(1).strip() if m else None return { "name": grab(r"Name:\s*(.+)"), "phone": grab(r"Phone:\s*([+\d][\d\s-]+)"), "email": grab(r"Email:\s*(\S+@\S+)"), "listing_ref": grab(r"Ref(?:erence)?:\s*(\S+)"), "message": grab(r"Message:\s*([\s\S]+?)\n\s*\n"), } PARSERS = {"leads@portal-a.example": parse_portal_a} def parse(email): parser = PARSERS.get(email.sender) if parser is None: return route_to_human(email, reason="unknown_sender") record = parser(email.text_body) record.update(source=email.sender, raw_id=email.message_id, received_at=email.date) return record Validate before you store
REQUIRED = ("listing_ref",) ONE_OF = ("phone", "email") def validate(r): problems = [f for f in REQUIRED if not r.get(f)] if not any(r.get(f) for f in ONE_OF): problems.append("no_contact") return problems If validate returns anything, send the email to a person with the reason attached. Do not guess.
Rules that save pain later
- Keep the raw email ID so you can re-parse when a portal changes its format, which happens without notice.
- Normalise phone numbers to one format before matching against existing contacts.
- Fail loudly. A parser that silently returns
Nonefor the phone number creates leads nobody can call. - Use AI only for the free-text message. Pulling budget, move-in date or "wants to view this weekend" from the message is a good AI job. Store that output in separate fields so it never overwrites what you parsed deterministically.
- Write a test per portal using a saved real email with personal details removed.
Dedupe and follow-up timers come after this step. They are much easier when every lead arrives in the same shape.
Disclosure: I run NxFlowAI, an AI automation agency. Mapping intake like this is part of our first audit, which takes 72 hours for one workflow.