# Gmail Search & Send — Verified Patterns (LFCS)

## Verified working: direct Python calls

When `google_api.py` fails or shows misleading errors, use direct REST calls via urllib:

```python
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request
import urllib.request, json, urllib.parse
from email.mime.text import MIMEText
import base64

creds = Credentials.from_authorized_user_file('/root/.hermes/google_token.json',
    ['https://www.googleapis.com/auth/gmail.readonly',
     'https://www.googleapis.com/auth/gmail.send'])
creds.refresh(Request())  # idempotent even if token is still valid

# --- Search emails ---
query = urllib.parse.quote('from:Robert.Logan@decprojects.com.au after:2026/05/11')
url = f'https://gmail.googleapis.com/gmail/v1/users/me/messages?q={query}&maxResults=10'
req = urllib.request.Request(url, headers={'Authorization': f'Bearer {creds.token}'})
with urllib.request.urlopen(req) as r:
    data = json.loads(r.read())
    for m in data.get('messages', []):
        print(m['id'])

# --- Read message metadata ---
msg_url = f'https://gmail.googleapis.com/gmail/v1/users/me/messages/{msg_id}?format=metadata&metadataHeaders=From&metadataHeaders=Subject&metadataHeaders=Date'
# or include metadataHeaders=Body for snippet

# --- Send email ---
msg = MIMEText("Email body text here")
msg['to'] = 'admin@lfcs.com.au'
msg['subject'] = 'Subject line'
raw = base64.urlsafe_b64encode(msg.as_bytes()).decode()
service = build('gmail', 'v1', credentials=creds)
result = service.users().messages().send(userId='me', body={'raw': raw}).execute()
```

## Critical: `from:` operator matches email address, not display name

**Gmail `from:tenders.dec` returned 0 results** even though emails existed from this sender. The display name was "DEC Projects" or similar, but the actual sender email was `Robert.Logan@decprojects.com.au`. Gmail's `from:` searches the envelope sender address.

**Rule: always search by the known email address, not the display name or domain fragment.**

Workaround for unknown exact sender: use `subject:tender` or broader search, then inspect message headers to find the actual from address, then re-search with the exact address.

## Confirm account identity before debugging search failures

```python
profile_url = 'https://gmail.googleapis.com/gmail/v1/users/me/profile'
# GET with Bearer token → returns emailAddress, messagesTotal, threadsTotal
```
Always check which account the token is connected to before assuming "no results" means no emails.

## google_api.py vs direct calls

`google_api.py` (gws CLI) prints errors to stderr even when the underlying API call succeeds. The `opsman-lfcs-27665` error from gws is a routing issue in the CLI wrapper — the actual Gmail API call via direct Python often works fine. When in doubt: ignore gws error output, run the direct Python pattern above.

## himalaya email client

Install: `curl -sSL https://raw.githubusercontent.com/pimalaya/himalaya/master/install.sh | PREFIX=~/.local sh`
Requires config at `~/.config/himalaya/config.toml` with IMAP/SMTP credentials. Not currently configured for LFCS — uses Google Workspace OAuth instead.

## Forward message as attachment (RFC822) — verified 2026-05-12

Use the raw endpoint to get original MIME bytes, then send via `messages/send` with a wrapped RFC822 payload:

```python
# Get raw message
url = f'https://gmail.googleapis.com/gmail/v1/users/me/messages/{msg_id}?format=raw'
req = urllib.request.Request(url, headers={'Authorization': f'Bearer {creds.token}'})
with urllib.request.urlopen(req) as r:
    data = json.loads(r.read())
    raw_b64 = data['raw']
    pad = len(raw_b64) % 4
    if pad: raw_b64 += '=' * (4 - pad)
    msg_bytes = base64.urlsafe_b64decode(raw_b64)

original = email.message_from_bytes(msg_bytes)
orig_subject = original.get('Subject', '(no subject)')

# Wrap and send as RFC822 forward
fwd_raw = (f"From: michael.mcloughlin@lfcs.com.au\r\n"
           f"To: admin@lfcs.com.au\r\n"
           f"Subject: Fwd: {orig_subject}\r\n"
           f"Content-Type: message/rfc822; charset=utf-8\r\n\r\n").encode('utf-8') + msg_bytes
b64_fwd = base64.urlsafe_b64encode(fwd_raw).decode('ascii').rstrip('=')

send_req = urllib.request.Request(
    'https://gmail.googleapis.com/gmail/v1/users/me/messages/send',
    data=json.dumps({'raw': b64_fwd}).encode(),
    headers={'Authorization': f'Bearer {creds.token}', 'Content-Type': 'application/json'}
)
with urllib.request.urlopen(send_req) as r:
    result = json.loads(r.read())
    print('Sent:', result.get('id'))
```

Send each message as a separate call — loops cause timeouts.