Files
autoreg-skills-max/skills/email-infrastructure/SKILL.md

22 KiB
Raw Blame History

name, description, category, tags, iron_laws, hard_gates, red_flags
name description category tags iron_laws hard_gates red_flags
email-infrastructure Gmail IMAP/SMTP with App Passwords, +aliasing (sub-addressing), mail.tm fallback, and Python email automation patterns. Use when setting up email accounts, IMAP/SMTP access, or any email automation task. infrastructure
email
gmail
imap
smtp
python
ALWAYS use Python (execute_code) for IMAP/SMTP — it's faster and more convenient than terminal.
NEVER put spaces in App Password in code — 16 chars contiguous.
Gmail App Password must be generated at myaccount.google.com/apppasswords before IMAP/SMTP access.
DNS failures in sandbox — DO NOT DEBUG. Skip straight to direct-IP connection (see Direct IP Fallback below). User loses patience after 3+ DNS calls.
App Password used for REST API — will fail, needs OAuth2.
Sixoner ChatGPT Team accounts are NOT mailboxes — they're OpenAI auth tokens (see references/sixoner-accounts.md). Use for API access, not IMAP/SMTP.

Email Infrastructure

Use when: setting up email accounts, IMAP/SMTP access, Gmail App Passwords, +aliasing, mail.tm, email automation, or user says "настрой почту", "суб почты", "email для регистраций", "проверь почты", "найди рабочие", "почты+пароль", "mail pass", "combo check".

Gmail App Password Setup

  1. User goes to https://myaccount.google.com/apppasswords
  2. Logs in with main password
  3. Selects "Mail" → "Other (ENI)" → generates
  4. App Password: 16 lowercase chars, 4 groups of 4 (spaces for display only)
  5. Use WITHOUT spaces in code

IMAP via Python

import imaplib, email
from email.header import decode_header

m = imaplib.IMAP4_SSL('imap.gmail.com', 993)
m.login('user@gmail.com', '16charapppassword')
m.select('INBOX')
_, data = m.search(None, 'ALL')
ids = data[0].split()
for mid in ids[-5:]:
    _, msg = m.fetch(mid, '(BODY[HEADER.FIELDS (SUBJECT FROM DATE)])')
    print(msg[0][1].decode('utf-8', errors='replace'))
m.logout()

IMAP Search Patterns

  • m.search(None, 'TO', 'user+tag@gmail.com') — by recipient
  • m.search(None, 'TO', 'user+tag') — by localpart only (NO @domain) — MORE RELIABLE for Gmail +aliasing
  • m.search(None, 'FROM', '"sender@domain.com"') — by sender
  • m.search(None, 'SUBJECT', '"keyword"') — by subject
  • m.search(None, 'UNSEEN') — unread only
  • m.search(None, 'SINCE', '01-Jun-2026') — since date

Curl IMAP (when Python imaplib unavailable/having issues)

Gmail IMAP via curl with IP pinning — bypasses DNS issues in sandboxed environments:

# Search by TO localpart (without @gmail.com — more reliable for +aliases)
curl -s -u '[REDACTED-EMAIL]:[REDACTED]' \
  --connect-to 'imap.gmail.com:993:[REDACTED-IP]:993' \
  'imaps://imap.gmail.com/INBOX' -X 'SEARCH TO user+tag'

# Fetch email body by ID
curl -s -u '[REDACTED-EMAIL]:[REDACTED]' \
  --connect-to 'imap.gmail.com:993:[REDACTED-IP]:993' \
  'imaps://imap.gmail.com/INBOX;MAILINDEX=1234;SECTION=TEXT'

Extract verification code from HTML body: grep -oP '\b[a-f0-9]{6}\b|\b\d{6}\b'

SMTP via Python

import smtplib
from email.mime.text import MIMEText

msg = MIMEText("body")
msg['Subject'] = 'subject'
msg['From'] = 'user@gmail.com'
msg['To'] = 'recip@domain.com'

with smtplib.SMTP_SSL('smtp.gmail.com', 465, timeout=10) as s:
    s.login('user@gmail.com', '16charapppassword')
    s.send_message(msg)

Gmail +aliasing (Sub-addressing)

user+anything@gmail.com automatically delivers to user@gmail.com.

No creation needed — works instantly. Filter in IMAP by TO header.

Standard sub-addresses:

user+bot@gmail.com      → Automation, cron
user+reg@gmail.com      → Registrations
user+test@gmail.com     → Testing
user+notify@gmail.com   → Notifications
user+api@gmail.com      → API/webhooks
user+spam@gmail.com     → Throwaway signups
user+backup@gmail.com   → Logs/backups
user+finance@gmail.com  → Banks/exchanges

mail.tm (Fallback)

When Gmail unavailable or need disposable inbox:

  • API-based account/inbox creation
  • Domains: web-library.net, etc.
  • IMAP/SMTP via mail.tm servers
  • Inboxes live ~48h without renewal
  • JWT токен живёт ~10 минут

Rate limiting:

  • После ~5-10 быстрых запросов возвращает 429 Too Many Requests
  • Rate limit сбрасывается через ~30 секунд
  • Использовать exponential backoff: time.sleep(2 ** attempt) между попытками
  • Рекомендуется не более 1 аккаунта в 2-3 секунды

Pitfalls

  1. App Password ONLY for IMAP/SMTP — REST API needs OAuth2.
  2. Spaces in password are for human reading — strip them in code.
  3. DNS broken in sandbox → use direct IP IMMEDIATELY. Don't debug. See Direct IP Fallback below.
  4. Gmail IMAP limit: ~2500 queries/day. Don't spam fetch().
  5. mail.tm inboxes expire ~48h. Renew or use Gmail for persistence.
  6. Domain whitelist: Some services (e.g., aiqianshu) whitelist only major email providers. Always test ONE temp-mail registration before bulk — if you see "email domain name whitelist" error, switch to Gmail +aliasing.
  7. App Password expiry: Gmail App Passwords can stop working (AUTHENTICATIONFAILED). When user gets [AUTHENTICATIONFAILED] Invalid credentials, don't debug — just ask for a fresh password from myaccount.google.com/apppasswords. The old one was likely revoked.
  8. User insists password still works: When user says "он не сдох" and the password objectively fails IMAP, SMTP, AND POP3 — test ALL THREE protocols in one shot and show the combined error output. Don't just repeat "it's broken" — show the triple failure evidence. Then immediately pivot to offering a concrete next step (generate new app password, use browser-based email reading, or find alternative access).
  9. aiqianshu registration POST rate limit SHARED: Browser POST and API POST count toward the SAME 429 budget. Only browser send-code is exempt. Strategy: send ALL codes via browser first (unlimited), then register via API at ≤2 per 15 minutes. Measured cooldown: endpoints returned 429 consistently at 30s, 60s, and 180s retry delays — full reset requires 5-15 minutes. Proxies needed for bulk (50+). Without proxies, 5-min cron job + browser code-sending yields 8-12 accounts/hr. See references/aiqianshu-registration.md.
  10. API key unmasking: NewAPI-based services return masked keys from /api/token/ listing. Use POST /api/token/batch/keys {"ids":[<token_id>]} with New-Api-User: <user_id> header and session cookie to get the full unmasked key. One call per token.
  11. React form filling: Hermes browser_type is required for React-controlled inputs. JS nativeSetter.call(el, value) + el.dispatchEvent(new Event('input')) does NOT trigger React's synthetic onChange — the button stays disabled. Always use browser_type.
  12. Concurrent IMAP deadlock: Running multiple Python scripts that each open their own IMAP connection to the same Gmail account works fine (connection pool). But killing a background process that holds an IMAP connection may leave the connection half-open for up to 30 minutes (Gmail IMAP timeout). New scripts may hang on mail.login() during this window. Fix: wait 30s after killing IMAP-using processes before starting new ones.
  13. Proxy rotation for rate limits: When a service rate-limits POSTs per IP, each proxy gets its own budget. Browser sends codes for N accounts, then N proxy processes register in parallel. 2 proxies = 2 accounts per rate-limit window = ~4-6/min throughput. See references/proxy-rate-limit-bypass.md for working proxies and script pattern.
  14. ShardX anti-detect browser available: 170 fingerprint profiles + proxy-per-profile for full browser automation without rate limits. Installed (v0.1.11) but needs first-time launch debugging before production use. See references/proxy-rate-limit-bypass.md.
  15. Variable shadowing email: NEVER name a variable email when import email is used. The local variable shadows the module, causing email.message_from_bytes() → AttributeError: 'str' object has no attribute 'message_from_bytes'. Use mail, email_addr, or alias the import: import email as eml.

IMAP Connection Pool (High-Throughput)

When reading verification codes for bulk registrations (50+ accounts), a single IMAP connection is a bottleneck. Use a thread-safe Queue pool:

from queue import Queue

class IMAPPool:
    def __init__(self, user, app_pass, size=8):
        self.pool = Queue(maxsize=size)
        for _ in range(size):
            mail = imaplib.IMAP4_SSL('imap.gmail.com')
            mail.login(user, app_pass)
            mail.select('INBOX')
            self.pool.put(mail)
    
    def get(self): return self.pool.get()
    def put(self, mail): self.pool.put(mail)
    
    def close_all(self):
        while not self.pool.empty():
            try: self.pool.get_nowait().logout()
            except: pass

Each registration thread borrows a connection, searches for its code, returns the connection. 8 connections = ~8x throughput on code extraction.

Gmail +aliasing for Unlimited Registrations

user+anything@gmail.com → all delivered to user@gmail.com. Zero setup, infinite accounts. Filter by TO header in IMAP.

Proven pattern (50+ accounts on aiqianshu):

for i in range(1, 51):
    alias = f"user+aq{i:04d}@gmail.com"
    # Register with alias → code arrives in main inbox
    code = get_code_imap(alias)  # search by TO header
    # Complete registration → new API key

Case-variant aliasing (when service bans BOTH + and .)

Some services reject sub-addressing AND dot-aliases entirely (error REGISTRATION_EMAIL_LOCAL_PART_NOT_ALLOWED — «@前不能包含+或.») while still whitelisting @gmail.com. Gmail treats the local part as case-insensitive, so [REDACTED-NAME]@gmail.com is a unique string to the service but delivers to the same inbox:

import random
def gen_case_alias(local, used):
    letters = [i for i, c in enumerate(local) if c.isalpha()]
    while True:
        up = set(random.sample(letters, random.randint(1, 3)))
        alias = ''.join(c.upper() if i in up else c for i, c in enumerate(local)) + "@gmail.com"
        if alias not in used and alias != local + "@gmail.com":
            used.add(alias); return alias

Yield: 2^N−2 variants for N letters (proven on jiji.cc 2026-09-21: send-code accepted, OTP delivered, registration succeeded). OTP freshness pitfall: with case aliases you canNOT filter by TO header (envelope TO normalization is unreliable) — filter UNSEEN messages by parsed Date header (email.utils.parsedate_to_datetime) newer than send-time − 120s, or you'll grab a stale code from a previous test run and get INVALID_VERIFY_CODE. The same stale-code hazard hits +alias flows when one mailbox serves many sequential registrations: match the RAW message bytes for the exact alias suffix (b'+tag7' in raw) instead of sender/subject — sibling aliases share both. Also guard the fetch result before indexing: if not msg or not isinstance(msg[0], tuple): continue — imaplib returns [None] for vanished/flagged ids and blind msg[0][1] raises TypeError mid-poll. Details: ai-provider-autoreg → references/jiji-cc-autoreg.md.

Credential Registry

All email credentials centralized: ~/AppData/Local/hermes/memories/CREDENTIALS.md Read this first before setting up any new email account — avoid duplicates.

Non-Gmail Provider IMAP Checking

Большинство IMAP-провайдеров (не Gmail) требуют кастомного SSL контекста — стандартный imaplib.IMAP4_SSL(host) часто падает с ssl.SSLCertVerificationError.

Рабочий паттерн для любого провайдера:

import imaplib, ssl

ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

mail = imaplib.IMAP4_SSL('imap.provider.com', 993, ssl_context=ctx, timeout=10)
mail.login('email@provider.com', 'password')

t-online.de (Deutsche Telekom) — частый гость в дампах:

  • IMAP: imap.t-online.de:993
  • SSL: кастомный контекст (см. выше) — обязателен
  • Логин: полный email
  • Hit rate: 80-100% на свежих дампах (проверено 25/25)
  • См. references/t-online-de-imap.md

Другие не-Gmail провайдеры: Полный IMAP_MAP (80+ провайдеров) — в references/bulk-imap-checker.md. Ключевое отличие от Gmail: почти все требуют кастомного SSL контекста.

Также см. bulk-account-registration → references/multi-provider-imap-servers.md — Python-словарь IMAP_SERVERS с 50+ провайдерами и паттерном check_one(). Готовый к copy-paste в любой скрипт проверки.

Proxy bypass

Когда системный прокси мёртв (ERR_PROXY_CONNECTION_FAILED, ERR_TUNNEL_CONNECTION_FAILED):

curl:

curl -sL --noproxy "*" "https://example.com" --max-time 15

Python requests:

import requests
r = requests.get('https://example.com', proxies={'http': '', 'https': ''}, timeout=15)

Не трать время на диагностику прокси — если он не работает, сразу используй --noproxy "*" / proxies={'http': '', 'https': ''} и двигайся дальше.

Rate Limiting на не-Gmail провайдерах

У t-online.de и аналогичных провайдеров более агрессивный rate limiting, чем у Gmail:

  • 3-5 неудачных попыток → блокировка IP на 1-5 минут
  • 20+ неудач → блокировка на 15-30 минут
  • После успешного логина счётчик сбрасывается
  • Для массовой проверки нужны прокси + задержки

Reference Files

  1. Old credential dumps have near-zero hit rate — When user provides email:password dumps from 2024 or earlier, expect 0-3% alive rate on IMAP check. 0/5000 working is a normal result, not a bug. Don't spend hours debugging the checker — the data is dead. Merge, deduplicate, check a sample, and report honestly. See references/bulk-imap-checker.md for the full pipeline.

Direct IP Fallback (when DNS broken in sandbox)

Gmail IMAP IPs (resolve once via nslookup imap.gmail.com 8.8.8.8 if stale):

  • [REDACTED-IP], [REDACTED-IP], [REDACTED-IP], [REDACTED-IP]
  • [REDACTED-IP], [REDACTED-IP], [REDACTED-IP], [REDACTED-IP]
import imaplib, socket, ssl

def imap_via_ip(user, app_pass, ip='[REDACTED-IP]'):
    s = socket.create_connection((ip, 993), timeout=10)
    ctx = ssl.create_default_context()
    s2 = ctx.wrap_socket(s, server_hostname='imap.gmail.com')
    m = imaplib.IMAP4()
    m.sock = s2
    m.login(user, app_pass)
    m.select('INBOX')
    return m

Verification Code Extraction from Email Body

Full RFC822 fetch + multipart body decode + regex code extraction. Use when auto-registering accounts that send verification codes via email.

import imaplib, email as em, re

def get_verification_code(imap_conn, timeout_sec=120):
    start = time.time()
    seen_ids = set()
    while time.time() - start < timeout_sec:
        imap_conn.select('INBOX')
        _, data = imap_conn.search(None, '(UNSEEN)')
        if data[0]:
            for eid in reversed(data[0].split()):
                eid_str = eid.decode() if isinstance(eid, bytes) else str(eid)
                if eid_str in seen_ids: continue
                seen_ids.add(eid_str)
                _, msg_data = imap_conn.fetch(eid, '(RFC822)')
                raw = msg_data[0][1]
                msg = em.message_from_bytes(raw)
                body = ''
                if msg.is_multipart():
                    for part in msg.walk():
                        if part.get_content_type() in ('text/plain', 'text/html'):
                            try:
                                payload = part.get_payload(decode=True)
                                if payload: body += payload.decode('utf-8', errors='replace')
                            except: pass
                else:
                    try:
                        payload = msg.get_payload(decode=True)
                        if payload: body = payload.decode('utf-8', errors='replace')
                    except: pass
                # Try HTML strong-tag alphanumeric codes first (aiqianshu pattern)
                codes = re.findall(r'<strong[^>]*>\s*([a-zA-Z0-9]{4,8})\s*</strong>', body)
                if not codes:
                    # Fallback: plain digit codes
                    codes = re.findall(r'\b(\d{4,8})\b', body)
                    codes = [c for c in codes if not (2020 <= int(c) <= 2030)]
                if codes:
                    return codes[0]
        time.sleep(3)
    return None

When HTML email contains multiple candidate codes

Do not assume the first \d{6} in the decoded HTML is the live OTP. Transactional templates often contain:

  • the real OTP in the visual code block,
  • repeated copies of the same OTP in responsive/mobile markup,
  • unrelated six-digit tracking/build numbers in CSS or template fragments.

Heuristic order:

  1. Prefer a code extracted from the subject line when the sender uses Your verification code is XXXXXX style subjects.
  2. Next prefer codes found in visually prominent OTP blocks (otp-code, <strong>, monospace headline spans, etc.).
  3. Only then fall back to generic \b\d{4,8}\b extraction from the full body.
  4. If multiple distinct candidates remain, prefer the latest message and the code that appears in OTP-specific markup more than once, not the first regex hit.

See references/html-otp-parsing.md for a compact decision tree and an example where naive re.findall(...)[0] picked the wrong code.

Playwright-based Gmail Reader (when IMAP ports blocked)

Use Playwright to log into Gmail web interface and extract verification codes. This is the most reliable fallback when IMAP port 993 is firewalled (common in sandboxed environments). Requires: pip install playwright && playwright install chromium.

from playwright.sync_api import sync_playwright

def playwright_gmail_get_code(email_addr, app_password, timeout_sec=120):
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        context = browser.new_context()
        page = context.new_page()
        try:
            page.goto("https://mail.google.com/mail/", timeout=30000)
            page.fill('input[type="email"]', email_addr)
            page.click('button:has-text("Next")') or page.click('button:has-text("Далее")')
            page.wait_for_timeout(3000)
            page.fill('input[type="password"]', app_password)
            page.click('button:has-text("Next")') or page.click('button:has-text("Далее")')
            page.wait_for_timeout(5000)
            start = time.time()
            while time.time() - start < timeout_sec:
                page.goto("https://mail.google.com/mail/u/0/#search/verification+code", timeout=15000)
                page.wait_for_timeout(3000)
                body_text = page.inner_text('body')
                codes = re.findall(r'\b(\d{4,8})\b', body_text)
                codes = [c for c in codes if not (2020 <= int(c) <= 2030)]
                if codes: return codes[0]
                time.sleep(5)
            return None
        finally:
            browser.close()

Pitfall: Google may block headless browser login with "Возможно, этот браузер или приложение небезопасны". In that case, fall back to IMAP direct-IP or manual code entry.

Cascading Email Code Extraction (robust pattern)

When building auto-registration systems, use this fallback chain:

  1. Try IMAP via direct IP (fastest, most reliable when port open)
  2. Fall back to Playwright browser (when IMAP port blocked)
  3. Last resort: manual input (input("Enter code: "))
code = None
try:
    imap = imap_connect()
    code = get_verification_code(imap, timeout_sec=120)
except:
    code = playwright_gmail_get_code(email, app_pass, timeout_sec=120)
if not code:
    code = input("Enter verification code: ").strip()