Why This Is Harder Than It Looks
"Promotional email" sounds like it should be easy to identify. In practice, it is one of the trickiest classification problems in email filtering. Marketing platforms have spent two decades making their output look exactly like personal email so it doesn't get filtered. At the same time, transactional email — order confirmations, password resets, account alerts — often comes from the same domains and even the same sending infrastructure as the marketing mail.
If you tag too aggressively, you end up routing important transactional mail into a "Promotions" folder where users miss it. If you tag too leniently, marketing mail floods the inbox and the classification provides no real value. The trick is to require a combination of signals — no single indicator is sufficient on its own.
The Authentication Baseline
Before any promotional indicators are even considered, a message must pass three baseline checks: SPF, DKIM, and a valid MX record on the sender domain.
Why these three? Because legitimate bulk marketers care about deliverability. Mailchimp, SendGrid, Constant Contact, HubSpot, and every other reputable email service provider configures SPF and DKIM correctly for their customers — they have to, or their mail goes straight to spam at Gmail and Outlook. A message claiming to be from a marketing platform but failing SPF or DKIM is almost certainly a spoof, not a real promotion.
The MX record check is the third leg. A real business sending marketing mail has a real domain with real DNS, including MX records that accept replies. A spammer using a throwaway domain often skips MX setup because they have no intention of receiving anything back. Spam Killer caches MX lookups for 24 hours by default to avoid hammering DNS, with a configurable cache size.
The Promotional Indicators
Once a message passes the authentication baseline, Spam Killer counts promotional indicators. By default, at least one indicator must be present. The indicators are:
1. List-Unsubscribe header. RFC 8058 requires bulk senders to include a List-Unsubscribe header that gives the recipient a one-click way to opt out. Gmail and Outlook now require this header for bulk senders to deliver to the inbox at all. Its presence is one of the strongest signals that a message is bulk marketing.
2. Precedence: bulk header. An older convention but still widely used. Mailing list software (Mailman, Sympa) and many ESPs add this header automatically.
3. List-Id header. Identifies the message as part of a mailing list. Used by virtually all newsletter platforms.
4. Marketing sender pattern. The local-part of the From address matches one of the configurable patterns: newsletter@, marketing@, noreply@, no-reply@, updates@, notifications@, hello@, info@, and so on.
5. Marketing X-Mailer header. The X-Mailer header identifies the software that sent the message. Spam Killer recognizes Mailchimp, SendGrid, Constant Contact, HubSpot, Marketo, Mailgun, Postmark, ConvertKit, Klaviyo, and a long list of others.
6. Marketing infrastructure domain. Some senders use ESP infrastructure directly — sending from mailchimp.com, sendgrid.net, etc. The Return-Path or DKIM domain matching one of these is a strong promotional signal.
Why min_indicators Is Configurable
The default min_indicators: 1 setting tags any message with at least one promotional indicator (after passing auth checks). This is appropriate for most users — if a message has a List-Unsubscribe header, it is bulk mail, full stop.
Some environments need to be more conservative. Banks, healthcare providers, and other industries where transactional mail is critical often set min_indicators: 2 or even 3. A bank's transactional alert might come from noreply@bank.com (matching one indicator: marketing sender pattern) but have no List-Unsubscribe and no List-Id. Requiring multiple indicators prevents that mail from being tagged as [PROMOTION].
The trade-off: the more indicators you require, the more legitimate marketing mail will pass through unclassified. Choose based on which failure mode bothers you more.
What Stays Classified as Ham
Properly designed transactional mail typically has zero promotional indicators, even when it comes from a marketing-style sender. A password reset email from noreply@example.com has the marketing sender pattern indicator (1 point), but no List-Unsubscribe (you can't unsubscribe from password resets — there's no list), no List-Id, and no marketing X-Mailer because it's sent by the application's transactional mail provider. With min_indicators: 1 it would be tagged. With min_indicators: 2, it would not.
This is the central tension. The more strict your promotional rules, the more genuine bulk mail leaks into the inbox. The more lenient, the more transactional mail gets tagged. We recommend starting with the defaults and watching your [PROMOTION] folder for the first two weeks. If you see legitimate transactional mail being tagged, raise min_indicators. If you see marketing mail in your inbox, lower it.
Performance: MX Caching and Thread Safety
Promotional classification adds a DNS lookup to every message that gets past the auth checks. To prevent this from becoming a bottleneck, Spam Killer maintains a thread-safe MX cache with a configurable TTL (default 24 hours) and a maximum size of 10,000 entries. After the first message from a domain, subsequent lookups are a hash lookup with no network round-trip.
The cache is shared across all worker threads. The Python implementation uses a module-level dictionary protected by a lock; the C implementation uses a fixed-size open-addressed hash table protected by a pthread mutex. Both have been benchmarked under load and add negligible overhead per message.