---
name: imap-email-search-and-extract
description: "Search, extract, and process emails from IMAP inbox — flight itineraries, attachment PDFs, confirmation codes, and structured data from HTML/plain-text emails."
version: 1.2.0
author: ShoNuff
platforms: [linux]
metadata:
  hermes:
    tags: [email, imap, pdf, itinerary, flight, extraction]
---

# IMAP Email Search and Extract

Search the user's IMAP inbox for specific emails and extract structured data.

## Standard approach\n\n### Connection setup\n\nCredentials typically come from Himalaya password files (`/root/.config/himalaya/*.pass`). For WordPress-hosted sites (Apex Track Experience, ITPP customer sites), SMTP/IMAP credentials may instead be in the WordPress database — see `references/wordpress-smtp-credential-discovery.md`.\n\n```python
import imaplib, email
HOST = "mail.germainebrown.com"
PORT = 993
USER = "g@germainebrown.com"
PW = open("/root/.config/himalaya/g-germainebrown.pass").read().strip()

conn = imaplib.IMAP4_SSL(HOST, PORT)
conn.login(USER, PW)
conn.select("INBOX")
```

### Searching strategies

**By sender and subject:**
```python
status, data = conn.search(None, 'FROM "Copa Airlines" SUBJECT "Cartagena"')
```

**By date range:**
```python
status, data = conn.search(None, '(SINCE "28-May-2026" BEFORE "5-Jul-2026")')
```

**Broad OR search across multiple keywords:**
```python
status, data = conn.search(None, 'OR OR SUBJECT "Cartagena" SUBJECT "flight" SUBJECT "itinerary"')
```

**Body text match:**
```python
status, data = conn.search(None, 'BODY "Cartagena"')
```

**Chain multiple filters:**
```python
# Sender + keyword across subject + body
status, data = conn.search(None, 'FROM "Copa" OR SUBJECT "Cartagena" BODY "Cartagena"')
```

### Decoding email content

**Get body (plain text or HTML):**
```python
def get_body(msg):
    if msg.is_multipart():
        for part in msg.walk():
            ct = part.get_content_type()
            if ct in ("text/plain", "text/html"):
                payload = part.get_payload(decode=True)
                if payload:
                    return payload.decode('utf-8', errors='replace')
    else:
        payload = msg.get_payload(decode=True)
        if payload:
            return payload.decode('utf-8', errors='replace')
    return ""
```

**Strip HTML tags for readability:**
```python
import re
clean = re.sub(r'<[^>]+>', ' ', body)
clean = re.sub(r'\s+', ' ', clean).strip()
```

### Extracting attachments (PDFs, images)

```python
for part in raw.walk():
    fn = part.get_filename()
    if fn and fn.endswith('.pdf'):
        payload = part.get_payload(decode=True)
        with open("/tmp/output.pdf", 'wb') as f:
            f.write(payload)
```

### Parsing ticket/PDF data

After extracting a PDF attachment, parse with `pdfminer`:

```python
from pdfminer.high_level import extract_text
from io import BytesIO
text = extract_text(BytesIO(pdf_bytes))

# Extract structured info
for line in text.split('\n'):
    if 'Flight Number' in line or 'CM ' in line:
        flights.append(line.strip())
    if 'Departure' in line or 'Arrival' in line:
        # next line has the date/time
```

**Install:** `pip3 install pdfminer.six`

**See `references/pdf-attachment-extraction.md`** for Copa Airlines and other carrier-specific PDF extraction patterns, MIME multipart navigation, and pitfalls.

### Flight itinerary extraction patterns

Common fields to extract:

| Field | Pattern |
|---|---|
| Flight number | `CM XXX` (Copa), `AA XXXX` (American), `UA XXXX` (United) |
| Confirmation code | 6-char alphanumeric (e.g. `A4GSFD`) |
| Departure time | `\d{1,2}:\d{2} (AM|PM)` |
| Airport codes | 3-letter codes inbound from text: `(MCO|PTY|CTG|ATL|SAV|MIA|IAD)` |
| Dates | `July? \d{1,2},? 2026` |

### Travel itinerary assembly

For full trip planning from extracted email data (flights + rental cars + hotels + activity research), see `references/travel-itinerary-building.md`. Covers:

- Multi-sender flight extraction (Copa, Allegiant, Avis)
- Account number hunt from HTML statements (Avis Wizard #)
- Departure day timeline with drive time + traffic estimates
- Markdown itinerary template
- Pitfalls per carrier (Allegiant surveys, Copa MCO departure, AT&T SMS unreliability)
- Real-time inbox monitoring during trip planning (Avis verification code → confirmation chain)
- Travel plan adjustments by priority (same flight, PreCheck, no bags, gate arrival time)
- Global Entry return procedure: customs clearance at MCO with GE kiosks (~10 min for 4 people, kiosk takes ~2 min/person, skip regular line, kids under 12 go through with parent)

### Verification code reader

When a service emails a security code (Avis login, banking), the agent can read the inbox faster than the user can find the email. See `references/avis-verification-code.md`.

### Avis rental reservation extraction (email approval chain)

A common pattern: user starts an Avis reservation, Avis sends a security code to their inbox, user asks the agent to check email. The agent finds the security code email, the user logs in, and shortly after Avis sends reservation confirmations.

**Detection flow:**

1. User says "check email for rental" — search `FROM "Avis" SINCE last-24h`
2. Find the security code email (`Subject: "Your security code"`) — extract the code
3. Moment after user logs in, Avis emails two confirmation emails (one per rental leg)
4. Re-check inbox and extract reservation numbers, vehicle info, dates, costs

**Avis confirmation key fields:**
- Reservation # pattern: `\d+US\d+`
- Vehicle: "Ford Explorer or Similar"
- Pickup/Dropoff locations and times
- Estimated total (includes taxes/fees)

**Pitfalls:**
- Avis sends security code FIRST — user needs code to log in before confirmations appear
- Two separate confirmations arrive minutes apart (outbound leg + return leg)
- Times in confirmation are local time zone
- The estimated total includes taxes and fees

### Global Entry return procedure

A common pattern: user starts an Avis reservation, Avis sends a security code to their inbox, user asks the agent to check email. The agent finds the security code email, the user logs in, and shortly after Avis sends reservation confirmations.

**Detection flow:**

1. User says "check email for rental" — search `FROM "Avis" SINCE last-24h`
2. Find the security code email (`Subject: "Your security code"`) — extract the 6-digit code
3. Moment after user logs in, Avis emails **two confirmation emails** (one per rental)
4. Re-check and find them — extract reservation numbers, vehicle info, dates, costs

**Avis confirmation key fields:**

```python
# Extract from plain text:
re.search(r'Reservation #(\d+US\d+)', body)
re.search(r'Ford Explorer or Similar', body)
re.search(r'Pick Up Location[^\n]*\n(.*?)\n', body, re.DOTALL)
re.search(r'(\$[\d,]+\.\d{2})', body)
```

**Pitfalls:**
- Avis sends the security code email FIRST — the user needs the code to log in before confirmations come through
- Two separate confirmations arrive minutes apart (SAV→MCO one-way + MCO→SAV return)
- The estimated total includes taxes and fees — strip `$` and commas for clean display
- Times in confirmation are in local time zone (Eastern for SAV/MCO)

### Global Entry return procedure

When the user returns to the US with Global Entry, see `references/global-entry-return-procedure.md` for customs clearance timelines, kiosk procedure, and timeline placement in the itinerary.

### Drive time estimation (Savannah → Orlando)

For Savannah → MCO (~285 mi, Saturday July):

| Condition | Time |
|---|---|
| No traffic | 4h 15m |
| Light traffic | 4h 30m |
| Moderate | 5h |
| Heavy | 5h 30m |

Jacksonville I-95 southbound backs up around lunchtime (11 AM - 1 PM) on Saturdays. Best departure: 8:30 AM. Worst: after 9 AM hits Jacksonville lunch rush.

### Inbox cleaning — solicitor/trash triage

For bulk classification of inbox messages as solicitation vs. keep, see `references/inbox-solicitation-triaging.md` and the runnable script at `scripts/inbox-solicitation-cleaner.py`.

The script uses a multi-heuristic approach:

1. **Allowlist** — internal senders, known legit domains (never flagged)
2. **Subject-line keywords** — immediate signals ("insurance quote", "factoring", "load board")
3. **Body content by sender domain class** — free-email senders vs. business-domain senders get different treatment
4. **Combined signal accumulation** — threshold-based (≥2 signals = flagged)

**Known false positives** include government registration notices (UCR.gov, login.gov), bank product emails with trucking keywords in boilerplate, and Slack onboarding templates. See the reference for the full false-positive table and mitigation strategies.

**Move pattern:** COPY to target folder → STORE +FLAGS \\Deleted → EXPUNGE. Always use BODY.PEEK[] to avoid marking messages as read.

### Headers-only fetch (efficient preview)

```python
status, msg_data = conn.fetch(num, "(BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)])")
hdr = msg_data[0][1].decode('utf-8', errors='replace')
```

### WPForms form submission delivery verification

For WordPress sites using WPForms + WP Mail SMTP, see `references/wpforms-delivery-verification.md` for the complete pattern:
1. Query `wp_wpforms_entries` in the WordPress DB for registrant names/emails
2. Check the admin IMAP inbox for form notification emails (confirms pipeline triggered)
3. Search for bounce messages via `FROM "mailer-daemon"` or `FROM "postmaster"`
4. Cross-reference bounced recipient addresses with registrant emails
5. Trace SPF/DKIM failures from bounce body back to DNS records
6. Verify the SMTP relay IP is in the SPF record

## Pitfalls

- **Multiple flight confirmation emails have the same content** — Copa Airlines sends one PDF attachment per passenger. Both Germaine's and Anita's PDFs contain identical flight details; only the passenger name differs. To confirm both are on the same booking, compare the confirmation code (A4GSFD) across both emails.
- **Emails are HTML by default** — plain text version may be empty or missing. Check both `text/plain` and `text/html` parts.
- **PDFs are `multipart/mixed` subparts** — iterate `raw.walk()`, check `part.get_filename()` for `.pdf` extension.
- **IMAP search dates use `DD-Mon-YYYY` format** — e.g. `28-May-2026`, not `2026-05-28` or `05/28/2026`.
- **IMAP date search is based on internal date, not header Date** — Internaldate is when IMAP received the message, which is usually close to the Date header but not identical.
- **Email HTML may use inline styles** — stripping all tags removes styling but keeps content. CSS class names (`style-Xia0GT4t_-text`) are noise — filter them out.
- **Copies to self may have the same subject** — When the user forwards or BCCs themselves, you get duplicate-looking results. Check the Date/From fields to distinguish.
- **Mailer-Daemon bounces are multipart** — The original message is included as an attachment. The bounce notification is text/plain, the original is text/rfc822-headers. Parse the root part for the bounce reason, the attachment for the original subject/body.
- **`SPAM` folder not `INBOX.spam`** — IMAP folder names are case-sensitive. Check both `SPAM`, `INBOX.spam`, `Spam`, `Junk`.
- **Multipart with many image attachments** — Copa Airlines ticket receipts include 6-7 inline images (logos, QR codes) alongside the PDF. The PDF is the last or second-to-last part in the MIME tree.
- **HTML emails from Copa use content blocker redirects** — Links in the email go through `sendgrid.net` tracking URLs, not direct to Copa.com. Don't try to open them — extract data from the visible text in the email body.
- **`get_content_charset()` may return `None`** — Fall back to `utf-8` or `iso-8859-1` for encoded payloads.
