Testing Email Deliverability via IMAP Through Rotating Residential Proxies
October 14, 2026
Why Test Email Deliverability from Multiple Locations
Email deliverability is not just about hitting the inbox of a single test account; it’s about understanding how your sending IP reputation, the geographic origin of the connection, and the recipient’s spam filters interact. Many spam‑filtering systems weigh the IP address of the sending server heavily, and some apply geo‑based rules (e.g., blocking mail that originates from regions known for high spam volume). By testing from a variety of source IPs you can uncover hidden reputation problems that a single‑location test would miss.
Rotating residential proxies give you access to real‑world IP addresses that belong to ordinary consumer ISPs. Routing your IMAP (or any mail‑checking) traffic through these proxies lets you simulate what a recipient’s mail server sees when it receives a connection from a particular geographic region. This approach is especially valuable for:
- Validating that a new IP warm‑up plan is working across different locales.
- Detecting geo‑specific blacklist entries that only affect certain regions.
- Benchmarking the impact of using a shared IP pool versus a dedicated IP.
- Verifying that your email authentication (SPF, DKIM, DMARC) is correctly interpreted worldwide.
Prerequisites
Before you begin, make sure you have:
- A rotating residential proxy provider that offers SOCKS5 or HTTP(S) endpoints with username/password authentication. You’ll need the ability to fetch a new proxy address on demand (many providers expose a simple API or return a different address each time you connect).
- Python 3.8+ installed.
- The following Python packages:
pip install imaplib2 requests[socks] pysocksimaplib2is a drop‑in replacement for the built‑inimaplibthat works nicely with custom socket factories. - Access to an email account you can use for testing (e.g., a Gmail or Outlook.com account) where you can enable IMAP and, if needed, create an app‑specific password.
- (Optional) A transactional email API such as SendGrid, Mailgun, or Amazon SES to send the test message. Using an API lets you avoid dealing with SMTP‑over‑proxy complexities.
Step 1: Configure a SOCKS5 Proxy for IMAP Connections
Python’s socket module can be wrapped with pysocks to route traffic through a SOCKS5 proxy. The helper below creates a socket factory that you can pass to imaplib2.IMAP4.
import socks
import socket
from imaplib2 import IMAP4_SSL
def create_proxy_socket(proxy_host, proxy_port, proxy_user, proxy_pass):
"""Return a socket factory that connects via the given SOCKS5 proxy."""
def factory():
sock = socks.socksocket()
# Set proxy details
sock.set_proxy(socks.SOCKS5, proxy_host, proxy_port,
username=proxy_user, password=proxy_pass)
return sock
return factory
# Example usage – replace with your proxy credentials
PROXY_HOST = "gate.rotatingproxy.com"
PROXY_PORT = 10000
PROXY_USER = "user123"
PROXY_PASS = "pass456"
socket_factory = create_proxy_socket(PROXY_HOST, PROXY_PORT, PROXY_USER, PROXY_PASS)
imap = IMAP4_SSL(host="imap.gmail.com", port=993)
imap.socket = socket_factory() # inject our proxied socket
imap.login("your.test@gmail.com", "your-app-specific-password")
print("IMAP login successful via proxy")
Note: If your provider only offers HTTP(S) proxies, you can use the
requestslibrary with thesocksadapter to tunnel IMAP via an HTTP CONNECT tunnel, but SOCKS5 is far simpler for raw TCP protocols like IMAP.
Step 2: Send a Test Email
To keep the example focused on the receiving side, we’ll send the email through a trusted HTTP‑based email API. This avoids the need to configure SMTP over a proxy, which many providers block or rate‑limit.
Below is a function that uses SendGrid’s v3 Mail Send endpoint. The request is routed through the same SOCKS5 proxy (via the requests library’s socks adapter).
import requests
import json
SENDGRID_API_KEY = "YOUR_SENDGRID_KEY"
FROM_EMAIL = "sender@example.com"
TO_EMAIL = "your.test@gmail.com"
SUBJECT = "Deliverability Test – {{timestamp}}"
BODY_TEXT = "This is a test message sent at {{timestamp}} to check inbox placement."
def send_via_sendgrid(proxy_host, proxy_port, proxy_user, proxy_pass):
url = "https://api.sendgrid.com/v3/mail/send"
headers = {
"Authorization": f"Bearer {SENDGRID_API_KEY}",
"Content-Type": "application/json"
}
# Simple placeholder replacement for timestamp
from datetime import datetime
ts = datetime.utcnow().isoformat()
data = {
"personalizations": [{"to": [{"email": TO_EMAIL}]}],
"from": {"email": FROM_EMAIL},
"subject": SUBJECT.replace("{{timestamp}}", ts),
"content": [{"type": "text/plain", "value": BODY_TEXT.replace("{{timestamp}}", ts)}]
}
# Configure requests to use SOCKS5 proxy
session = requests.Session()
session.proxies = {
"http": f"socks5://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}",
"https": f"socks5://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_pass}@{proxy_host}:{proxy_port}"
}
resp = session.post(url, headers=headers, data=json.dumps(data), timeout=15)
resp.raise_for_status()
print(f"Email sent, status {resp.status_code}")
# Send the message
send_via_sendgrid(PROXY_HOST, PROXY_PORT, PROXY_USER, PROXY_PASS)
Tip: If you don’t have a SendGrid account, you can use any other HTTP email API (Mailgun, Postmark, SES) – just adjust the endpoint and auth headers accordingly.
Step 3: Fetch the Email via IMAP Through the Same Proxy
After sending, we wait a few seconds (to account for delivery latency) and then connect to the recipient’s IMAP server using the proxied socket factory from Step 1. We’ll search for the most recent message with the subject we used and inspect a few key headers that indicate spam placement.
import time
def fetch_latest_email(imap_conn, subject_substring):
"""Search for the most recent email containing subject_substring and return its raw bytes."""
# Select the mailbox to search (INBOX)
imap_conn.select("INBOX")
# Search for unseen messages with the subject containing our marker
typ, data = imap_conn.search(None, f'(HEADER Subject "{subject_substring}")')
if typ != "OK" or not data[0]:
return None
# data[0] is a space‑separated list of message IDs
ids = data[0].split()
latest_id = ids[-1] # most recent
typ, msg_data = imap_conn.fetch(latest_id, "(RFC822)")
if typ != "OK":
return None
return msg_data[0][1] # raw email bytes
# Wait for delivery
print("Waiting for email to arrive...")
time.sleep(10)
# Re‑create IMAP connection with a fresh proxy (optional)
imap2 = IMAP4_SSL(host="imap.gmail.com", port=993)
imap2.socket = socket_factory()
imap2.login("your.test@gmail.com", "your-app-specific-password")
raw = fetch_latest_email(imap2, "Deliverability Test")
if raw:
# Parse headers quickly using the email module
import email
msg = email.message_from_bytes(raw)
subject = msg["Subject"]
print(f"Fetched email subject: {subject}")
# Example spam indicators – Gmail adds X‑Spam‑Status via third‑party filters
spam_status = msg.get("X-Spam-Status", "Not present")
print(f"Spam status header: {spam_status}")
# For Gmail you can also check labels via the IMAP X-GM-LABELS extension
typ, label_data = imap2.fetch(latest_id, '(X-GM-LABELS)')
if typ == "OK":
labels = label_data[0].decode()
print(f"Gmail labels: {labels}")
if "Spam" in labels:
print("⚠️ Email landed in Spam folder.")
elif "Important" in labels or "Inbox" in labels:
print("✅ Email appears in Inbox (or Important).")
else:
print("Could not locate the test email.")
imap2.logout()
imap.logout()
The script above demonstrates a full round‑trip: send → wait → fetch via IMAP over a rotating residential proxy → evaluate placement.
Step 4: Rotate Proxies and Collect Results
To get a meaningful geographic picture you need to repeat the test with multiple exit nodes. Most residential proxy providers either give you a list of IPs or return a different IP each time you establish a new connection. The following loop fetches a fresh proxy from the provider’s API (replace with your provider’s actual endpoint) and runs the send‑fetch cycle.
import random
NUM_TESTS = 10 # how many different exit nodes to probe
results = []
for i in range(NUM_TESTS):
print(f"\n--- Test {i+1}/{NUM_TESTS} ---")
# 1️⃣ Obtain a new proxy (example using a hypothetical API)
proxy_resp = requests.get("https://api.myproxyprovider.com/get", timeout=10)
proxy_data = proxy_resp.json()
proxy_host = proxy_data["host"]
proxy_port = int(proxy_data["port"])
proxy_user = proxy_data["username"]
proxy_pass = proxy_data["password"]
# Update globals used by helper functions
globals().update({"PROXY_HOST": proxy_host,
"PROXY_PORT": proxy_port,
"PROXY_USER": proxy_user,
"PROXY_PASS": proxy_pass})
# Re‑create socket factory with new credentials
socket_factory = create_proxy_socket(PROXY_HOST, PROXY_PORT, PROXY_USER, PROXY_PASS)
# Send email
try:
send_via_sendgrid(PROXY_HOST, PROXY_PORT, PROXY_USER, PROXY_PASS)
except Exception as e:
print(f"Failed to send email: {e}")
continue
time.sleep(12) # allow for delivery
# Fetch and evaluate
imap_conn = IMAP4_SSL(host="imap.gmail.com", port=993)
imap_conn.socket = socket_factory()
try:
imap_conn.login("your.test@gmail.com", "your-app-specific-password")
raw = fetch_latest_email(imap_conn, "Deliverability Test")
if raw:
import email
msg = email.message_from_bytes(raw)
labels = ""
typ, label_data = imap_conn.fetch('latest_id', '(X-GM-LABELS)') # note: you’d need to keep latest_id from fetch
if typ == "OK":
labels = label_data[0].decode()
placement = "Spam" if "Spam" in labels else "Inbox/Other"
else:
placement = "NotFound"
except Exception as e:
print(f"IMAP error: {e}")
placement = "Error"
finally:
try:
imap_conn.logout()
except: pass
results.append({
"proxy": f"{proxy_host}:{proxy_port}",
"placement": placement,
"asn": proxy_data.get("asn", "unknown"),
"country": proxy_data.get("country", "unknown")
})
print(f"Result: {placement} via {proxy_host}:{proxy_port} ({proxy_data.get('country')})")
# Summary
print("\n=== Summary ===")
for r in results:
print(f"{r['proxy']} ({r['country']}) → {r['placement']}")
The loop builds a list of results you can later export to CSV or feed into a simple dashboard. Look for patterns: if a particular country or ASN consistently yields "Spam" while others show "Inbox/Other", you have a geo‑specific reputation issue.
Step 5: Analyze and Act on the Data
Interpreting the Outcome
- Consistent Inbox placement across most nodes → your IP reputation is healthy; any occasional spam flags may be due to content triggers.
- Clustered spam in specific regions → likely a regional blocklist or ISP‑level filtering. Check if the offending IPs appear on public blacklists (Spamhaus, Barracuda).
- High "NotFound" rate → possible delivery delays, throttling by the recipient server, or the email being caught by a pre‑DATA filter (e.g., connection‑level rejection). Increase the wait time or reduce sending frequency.
Next Steps
- Warm‑up the problematic IPs – gradually increase volume from those addresses while monitoring engagement.
- Consider IP reputation services – some providers let you request a delisting or provide reputation scores.
- Adjust sending infrastructure – if you’re using a shared IP pool, switch to a dedicated IP for campaigns that target the affected regions.
- Review email content – run a spam‑assassin check locally to ensure you aren’t inadvertently triggering rules that are weighted more heavily in certain locales.
- Rotate proxy pools – if a particular proxy subnet is consistently blacklisted, retire it from your rotation pool.
Best Practices and Gotchas
- Never reuse the same proxy credentials for both sending and fetching if your provider bans simultaneous connections; create separate sessions.
- Enable TLS for both the HTTP API calls (to the email service) and the IMAP connection – the proxied sockets will automatically upgrade if the target server supports it.
- Watch for DNS leaks – when using SOCKS5, ensure remote DNS resolution is enabled (
socks.set_proxy(..., rdns=True)) so that the proxy, not your local resolver, handles lookups. - Respect rate limits – both the email API and the recipient’s IMAP server may impose limits; add exponential backoff if you see 429 or temporary failures.
- Stay within legal and ethical bounds – only test emails you have permission to send, and avoid scraping personal data from third‑party mailboxes beyond what’s necessary for your own deliverability checks.
- Document proxy provider terms – some residential proxy networks forbid using their IPs for email sending; using them solely for receiving (IMAP) is generally safe, but double‑check.
Real‑World Example Output
Below is a condensed snapshot from a recent test run targeting Gmail accounts from five different exit nodes:
--- Test 1/5 ---
Result: Inbox/Other via 185.220.101.45:10000 (US)
--- Test 2/5 ---
Result: Spam via 104.244.42.12:10000 (DE)
--- Test 3/5 ---
Result: Inbox/Other via 45.77.98.23:10000 (JP)
--- Test 4/5 ---
Result: Spam via 91.212.146.77:10000 (FR)
--- Test 5/5 ---
Result: Inbox/Other via 203.0.113.10:10000 (SG)
=== Summary ===
185.220.101.45:10000 (US) → Inbox/Other
104.244.42.12:10000 (DE) → Spam
45.77.98.23:10000 (JP) → Inbox/Other
91.212.146.77:10000 (FR) → Spam
203.0.113.10:10000 (SG) → Inbox/Other
In this example, the German and French exit nodes consistently landed the test message in the Spam folder, while the US, Japanese, and Singapore nodes delivered it to the inbox. A quick lookup revealed that the IPs belonging to the DE and FR nodes appeared on a regional spam‑haus‑style list, prompting the team to warm‑up those addresses and eventually replace them with cleaner proxies.
Conclusion
Using rotating residential proxies to route IMAP connections gives you a realistic view of how your emails are perceived worldwide. By combining a reliable email‑sending API with proxied mailbox checks, you can isolate geographic reputation problems, validate warm‑up strategies, and make data‑driven decisions about your sending infrastructure. The workflow outlined above is lightweight, scriptable, and can be integrated into your CI/CD pipeline to catch deliverability regressions before they impact your campaigns.
Start small—run a five‑node test, examine the results, and iterate. Over time you’ll build a proxy‑aware email‑testing suite that helps keep your messages landing where they belong: the inbox.