English Help Legal Sign Up Log In

Building a Mass Email Sender with Python and gevent

Sending outbound email from your own infrastructure is still a common requirement for marketers in Sydney, Melbourne, and Brisbane who want full control of their stack. While commercial platforms handle everything for you, a custom solution built on Python and the gevent library gives you the throughput of a high-performance asynchronous model without the complexity of full asyncio. If you are familiar with cooperative scheduling, monkey-patching, and basic SMTP, you can have a working sender running before your flat white gets cold.

The real catch is balancing raw speed with local compliance. Australia's Spam Act 2003, enforced by the ACMA, sets clear rules about consent, sender identification, and unsubscribe headers. Any tool you build needs to respect those rules, otherwise complaints can be lodged and penalties reach into the millions. A Python sender is powerful, but it must be wired into a responsible sending policy from day one.

Choosing gevent over threading and asyncio

Threaded Python code suffers from the global interpreter lock, so spawning a thousand threads does not give you a thousand SMTP connections. gevent gets around this through lightweight greenlets that yield on I/O, such as DNS lookups, socket writes, and server greetings. Because greenlets are cooperatively scheduled, you can run thousands inside a single OS process, which is friendly to machines sitting on an Aussie NBN connection that often caps upload bandwidth.

asyncio is the modern alternative and works well with libraries like aiosmtplib, but it requires code to be structured around coroutines, awaits, and an event loop. If your existing sending logic is synchronous or you want to wrap older IMAP and SMTP helpers, gevent's monkey-patching makes migration painless. You patch the standard library at startup, then write code that looks synchronous yet behaves asynchronously under the hood.

Building the core sending loop

At the heart of any high-throughput sender is a small loop that pulls recipients from a queue, opens an SMTP connection, pushes the prepared message, and closes the socket. With gevent, the queue itself can be a gevent.queue.Queue, which yields naturally when empty. Spawning a pool of greenlets with gevent.pool.Pool lets you control concurrency against the limit your SMTP relay can handle, often a figure negotiated with your upstream provider in Perth or Adelaide.

A compact skeleton often looks like this:

from gevent import monkey; monkey.patch_all()
import gevent
from gevent.queue import Queue

jobs = Queue()
for r in recipients:
    jobs.put(r)

def worker():
    while not jobs.empty():
        recipient = jobs.get()
        send_one(recipient)

pool = gevent.pool.Pool(size=50)
for _ in range(50):
    pool.spawn(worker)
pool.join()

Greenlets cooperate on socket I/O, so sending tens of thousands of mixed-personalisation messages from a single VPS is realistic when the relay tolerates burst traffic. For deeper configuration ideas, many operators share working snippets inside the active forum at the outreach automation board, where threads dive into relay rotation and DNS warm-up.

Async libraries at a glance

Before committing to gevent, it pays to weigh it against the other asynchronous options available in the Python ecosystem. Thread-based executors are simple but blocked by the GIL, asyncio is the modern default but requires deep rewrites, and Twisted is mature but heavy. The table below summarises how each approach behaves for high-volume SMTP work.

Library Concurrency model Threading model Stdlib patching required Learning curve Best fit
gevent + monkey-patch Cooperative greenlets OS threads, blocked by GIL Yes, full stdlib patch Moderate Wrapping legacy sync code
asyncio + aiosmtplib Native coroutines Single-thread event loop No Steep Greenfield async apps
concurrent.futures.ThreadPoolExecutor Pre-emptive threads Native threads No Low Sends under a few hundred
Twisted + twisted.mail Reactor pattern Single-thread reactor No Steep Mature transport stacks

For most custom senders that need to hit the middle ground between speed and maintainability, gevent lands as the pragmatic choice, especially when paired with gevent.lock.Semaphore for per-domain throttling against Aussie relays such as Telstra or Optus, which silently drop traffic above a few hundred connections per minute.

Compliance, monitoring, and scaling in Australia

A sender that floods the internet with messages will quickly land on blocklists such as Spamhaus or the Australian Spam Intelligence Database. You need to parse SMTP response codes, classify hard bounces (5xx) from soft bounces (4xx), and feed those signals back into a suppression list. gevent makes this trivial because each delivery attempt is just one more greenlet; you collect results in a shared container and decide later whether to retry.

Compliance work in Australia goes beyond bounces. The Spam Act mandates that every commercial email include a functional unsubscribe link, accurate sender details, and that consent be either express or inferred in defined situations. Operators also need to register with the ACMA when using local marketing lists and honour opt-outs within five business days, a deadline that is shorter than the overseas norm. Storing consent records alongside the recipient row lets you prove compliance if a complaint is investigated.

Once the sender is stable on a single machine, you can scale out by sharding the recipient list across nodes and running the same gevent pool on each shard. Centralised logging through syslog, Loki, or simply writing to a shared NFS volume lets a team in different timezones, from Sydney to Perth, spot issues that only appear at certain hours of the morning. In production, alerts should fire when the bounce rate climbs above two percent, when the queue depth stops draining, or when a relay returns persistent authentication errors.

For larger operations, an external eye on credential rotation, IP reputation tracking, and warm-up schedules keeps the infrastructure healthy across weeks and months of sending. Builders running commercial outreach kits often borrow inspiration from the Ilusual monitoring suite, which exposes reputation, queue health, and reply rates in real time.

Recommendations before going live

Before pushing a new sender onto an Australian list, a few operational habits save a lot of heartache down the track. Some of these are enforced by law, others come from years of community trial and error.

  • Pin and audit every dependency so a malicious package update never lands in the sender.
  • Warm up new IPs gradually over two to four weeks rather than firing tens of thousands of emails on day one.
  • Store SMTP reply codes per recipient and auto-suppress 5xx addresses.
  • Log timezone-aware timestamps in UTC, then convert for local Australian reporting.
  • Keep unsubscribe headers, sender identity, and physical address accurate to satisfy the ACMA.
  • Run a small canary list through the system before unlocking the full recipient pool.

Combining those habits with the technical stack above gives you a sender that behaves responsibly under Australian law while still moving at the pace gevent enables.