mailscript
mailscript is an email-filtering language and toolkit. Rules are written in Starlark — a small, deterministic, Python-like language — and evaluated against a message with 262 mail-aware builtins. It runs three ways: offline against a sample message, over a mailbox, or as an SMTP proxy in front of any mail server. It is MIT-licensed and ships as a single Go binary.
Inside the platform, mailscript is the policy engine (internal/mailscript/engine.go). Standalone, it is its own repository with a full language specification (SPEC.md).
Why it verifies instead of trusting headers
Authentication-Results is an ordinary header. Any sender can write spf=pass; dkim=pass; dmarc=pass into a message, and spammers do exactly that. A filter that reads that header and believes it is trivially bypassed. mailscript instead recomputes SPF, DKIM, DMARC, ARC and DANE from the message bytes and live DNS. The distinction is built into the language: is_verified() runs the real cryptographic check, while spf_result() only reports what an upstream claimed, and is meaningful only once auth_results_trusted() is true.
The execution model
Every script defines a single entry point, evaluate(). The engine runs it once per message inside resource limits (bounded steps and time) and reads the action the script chose. On error the engine fails closed. This is a real rule from the platform's example set:
def evaluate():
# Recompute every verdict locally from the message + DNS.
if forged_auth_results():
log_entry("message forged an authentication header")
quarantine()
return
if dns_available() and not is_verified():
add_score(4.0, "sender did not prove control of " + from_domain())
accept()Conditions: what a rule can inspect
Builtins cover the whole message and its provenance. A few of the common ones, grouped the way SPEC.md groups them:
Headers & content
get_header(name), get_body(), get_subject(), regex_match(pattern, text), urls(), attachments().
Identity & domains
from_domain(), envelope_from(), client_ip(), get_ip_reputation(), check_rbl(zone).
Authentication — verified vs reported
Verified (computed): is_verified(), check_spf(), check_dkim(), check_dmarc(), forged_auth_results(). As reported (untrusted unless auth_results_trusted()): spf_result(), dkim_result().
Classification
Human-vs-machine separation and a Fisher/Robinson + TF-IDF Bayesian classifier with an "unsure" band, so correspondence, bulk, transactional, list and automated mail can be treated differently.
Actions: what a rule can do
A script chooses one delivery outcome and can attach score and metadata along the way:
accept() # deliver normally
reject("reason") # refuse at SMTP time (5xx)
quarantine() # hold for review
add_score(n, "reason") # accumulate a spam score
add_header(k, v) # tag the message
log_entry("...") # structured audit line
dlp_scan() # data-loss-prevention passA scoring-style anti-spam rule reads reputation, recomputed auth, RBLs and content, sums a score, and lets a threshold decide:
def evaluate():
score = 0
if get_ip_reputation() < 30:
score += 5
if check_spf() == "fail": score += 3
if check_dkim() == "fail": score += 3
for rbl in ["zen.spamhaus.org", "bl.spamcop.net"]:
if check_rbl(rbl):
score += 4
if score >= 8:
quarantine()
else:
accept()Running as an SMTP proxy
The same script that filters a file can front a live mail server. The proxy accepts SMTP, runs the script on every message, and relays accepted mail to an upstream. This is how it sits on the perimeter in production (in front of the platform's mail-primary):
# Filter on submission/relay ports, forward clean mail upstream
mailscript proxy --script=filter.star --port=3025,3587 \
--upstream=mail-primary:2525 --enable-tls \
--cert=cert.pem --key=key.pem \
--clamav-addr=clamav:3310 --forward-quarantineIt also exposes a gRPC interface, an interactive REPL for developing rules (mailscript repl), offline inspection (mailscript inspect --eml=message.eml --verify), and a standalone verifier (mailscript verify --eml=… --client-ip=…). mbox and Maildir inputs and JSON output make it scriptable in a pipeline.
How it plugs into the platform
go-emailservice-ads embeds the Starlark engine as its policy layer, so the same rule language governs filtering and routing decisions inside the server — alongside Sieve scripts (examples/policies/sieve/) for classic mailbox filtering. Manage policies with adsemailadm policy. See the Platform guide for where policy evaluation sits in the pipeline.