#!/usr/bin/env python3 """ Email checker for heartbeat using Redis ID tracking. Tracks seen email IDs in Redis to avoid missing read emails. Stores emails to Qdrant with sender-specific user_id for memory. Only alerts on emails from authorized senders. """ import imaplib import email from email.policy import default import json import sys import redis import subprocess from datetime import datetime # Authorized senders with their user IDs for Qdrant storage # Add your authorized emails here AUTHORIZED_SENDERS = { # "your_email@gmail.com": "yourname", # "spouse_email@gmail.com": "spousename" } # Gmail IMAP settings IMAP_SERVER = "imap.gmail.com" IMAP_PORT = 993 # Redis config REDIS_HOST = "10.0.0.36" REDIS_PORT = 6379 REDIS_KEY = "email:seen_ids" # Load credentials CRED_FILE = "/root/.openclaw/workspace/.gmail_imap.json" def load_credentials(): try: with open(CRED_FILE, 'r') as f: return json.load(f) except Exception as e: return None def get_redis(): try: r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True) r.ping() # Test connection return r except Exception as e: return None def store_email_memory(user_id, sender, subject, body, date): """Store email to Qdrant as memory for the user.""" try: # Format as conversation-like entry email_text = f"[EMAIL from {sender}]\nSubject: {subject}\n\n{body}" # Store using background_store.py (fire-and-forget) script_path = "/root/.openclaw/workspace/skills/qdrant-memory/scripts/background_store.py" subprocess.Popen([ "python3", script_path, f"[Email] {subject}", email_text, "--user-id", user_id ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except Exception as e: pass # Silent fail def get_user_context(user_id): """Fetch recent context from Qdrant for the user.""" try: script_path = "/root/.openclaw/workspace/skills/qdrant-memory/scripts/get_user_context.py" result = subprocess.run([ "python3", script_path, "--user-id", user_id, "--limit", "3" ], capture_output=True, text=True, timeout=10) if result.returncode == 0 and result.stdout.strip(): return result.stdout.strip() except Exception as e: pass return None def check_emails(): creds = load_credentials() if not creds: return # Silent fail email_addr = creds.get("email") app_password = creds.get("app_password") if not email_addr or not app_password: return # Silent fail r = get_redis() if not r: return # Silent fail if Redis unavailable try: # Connect to IMAP mail = imaplib.IMAP4_SSL(IMAP_SERVER, IMAP_PORT) mail.login(email_addr, app_password) mail.select("inbox") # Get ALL emails (not just unseen) status, messages = mail.search(None, "ALL") if status != "OK" or not messages[0]: mail.logout() return # No emails email_ids = messages[0].split() # Get already-seen IDs from Redis seen_ids = set(r.smembers(REDIS_KEY)) # Check last 10 emails for new ones for eid in email_ids[-10:]: eid_str = eid.decode() if isinstance(eid, bytes) else str(eid) # Skip if already seen if eid_str in seen_ids: continue status, msg_data = mail.fetch(eid, "(RFC822)") if status != "OK": continue msg = email.message_from_bytes(msg_data[0][1], policy=default) sender = msg.get("From", "").lower() subject = msg.get("Subject", "") date = msg.get("Date", "") # Extract email body body = "" if msg.is_multipart(): for part in msg.walk(): if part.get_content_type() == "text/plain": body = part.get_content() break else: body = msg.get_content() # Clean up body (limit size) body = body.strip()[:2000] if body else "" # Check if sender is authorized and get their user_id user_id = None for auth_email, uid in AUTHORIZED_SENDERS.items(): if auth_email.lower() in sender: user_id = uid break # Mark as seen in Redis regardless of sender (avoid re-checking) r.sadd(REDIS_KEY, eid_str) if user_id: # Store to Qdrant for memory store_email_memory(user_id, sender, subject, body, date) # Get user context from Qdrant before alerting context = get_user_context(user_id) # Output for Kimi to respond (with context hint) print(f"[EMAIL] User: {user_id} | From: {sender.strip()} | Subject: {subject} | Date: {date}") if context: print(f"[CONTEXT] {context}") # Cleanup old IDs (keep last 100) all_ids = r.smembers(REDIS_KEY) if len(all_ids) > 100: # Convert to int, sort, keep only highest 100 id_ints = sorted([int(x) for x in all_ids if x.isdigit()]) to_remove = id_ints[:-100] for old_id in to_remove: r.srem(REDIS_KEY, str(old_id)) mail.close() mail.logout() except Exception as e: # Silent fail - no output pass if __name__ == "__main__": check_emails() sys.exit(0)