GoEmail Services

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 pass

A 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-quarantine

It 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.