sieve

package module
v0.0.0-...-cc74729 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 18, 2026 License: MIT Imports: 4 Imported by: 0

README

sieve-go

A pluggable Sieve mail filtering language implementation in Go. Designed to be embedded in mail-handling applications: parse a script, plug in your handler, route mail.

  • Compile / Validate / Run — separate parsing and validation from execution, so you can accept user-submitted scripts safely.
  • Extensible — actions, tests, and match-type tags live in a registry. Each IANA Sieve extension can be a small standalone package.
  • Mail-format agnostic — your application supplies a Message interface; a net/mail adapter is included.
  • No external dependencies.

Status

RFC 5228 core: lexer, parser, AST, registry, interpreter with if / elsif / else, stop, implicit keep, the standard actions (keep, discard, redirect), tests (address, header, exists, size), combinators (allof, anyof, not, true, false), match types (:is, :contains, :matches with * / ? globs), and address parts (:all, :localpart, :domain).

Bundled extensions:

Package Capability Spec
extensions/fileinto fileinto RFC 5232 (incl. :flags)
extensions/envelope envelope RFC 5228 §5.4
extensions/body body RFC 5173 (:raw/:text/:content)
extensions/imap4flags imap4flags RFC 5232 (full)
extensions/variables variables RFC 5229
extensions/regex regex draft-ietf-sieve-regex
extensions/mime mime RFC 5703 (:mime / :anychild / foreverypart / break / extracttext / replace / enclose)
extensions/reject reject, ereject RFC 5429
extensions/editheader editheader RFC 5293 (addheader / deleteheader)
extensions/vacation vacation RFC 5230
extensions/subaddress subaddress RFC 5233 (:user / :detail)
extensions/relational relational RFC 5231 (:count / :value)

The core also honours the standard :comparator tag with the i;ascii-casemap (default) and i;octet comparators from RFC 4790.

Install

go get github.com/hilli/sieve-go

Quick start

package main

import (
    "fmt"
    "log"

    "github.com/hilli/sieve-go"
    _ "github.com/hilli/sieve-go/extensions/fileinto" // enables `require ["fileinto"];`
    "github.com/hilli/sieve-go/message"
)

type Handler struct{ delivered string }

func (h *Handler) Keep() error               { h.delivered = "INBOX"; return nil }
func (h *Handler) FileInto(mb string) error  { h.delivered = mb; return nil }
func (h *Handler) Discard() error            { h.delivered = "/dev/null"; return nil }
func (h *Handler) Redirect(addr string) error { return nil }

func main() {
    script, err := sieve.Compile(`
        require ["fileinto"];
        if header :contains "Subject" "[oncall]" {
            fileinto "Oncall";
            stop;
        }
    `)
    if err != nil { log.Fatal(err) }

    msg := message.NewBuilder().
        AddHeader("From", "alice@example.com").
        AddHeader("Subject", "[oncall] disk full").
        Build()

    h := &Handler{}
    if err := script.Run(msg, h); err != nil { log.Fatal(err) }
    fmt.Println("delivered to:", h.delivered) // -> Oncall
}
Validate without running

For UIs that accept user-submitted scripts:

if err := sieve.Validate(userScript); err != nil {
    return fmt.Errorf("invalid sieve script: %w", err)
}

Validation catches unknown commands, unknown tests, missing require, malformed argument shapes, and similar problems before any message ever touches the script.

Run the examples
go run ./examples/simple
go run ./examples/attachments
echo 'keep;' | go run ./examples/validate
Detecting attachments

Load the mime extension and parse the raw message with message.ParseMIME, which surfaces MIME parts to :mime :anychild:

import (
    _ "github.com/hilli/sieve-go/extensions/fileinto"
    _ "github.com/hilli/sieve-go/extensions/mime"
)

script, _ := sieve.Compile(`require ["mime","fileinto"];
if header :mime :anychild :contains "Content-Disposition" "attachment" {
    fileinto "Attachments";
}`)
msg, _ := message.ParseMIME(rawMessageBytes)
script.Run(msg, handler)

Extending the language

Each Sieve extension is a separate Go package that self-registers on import. To enable one in scripts processed by the package-level sieve.Compile, just blank-import it:

import (
    _ "github.com/hilli/sieve-go/extensions/body"
    _ "github.com/hilli/sieve-go/extensions/regex"
)

…and scripts use require ["body", "regex"];.

For hosts that need an isolated registry (e.g. multiple tenants with different allowed extensions), build a fresh interpreter:

i := sieve.NewInterpreter()
fileinto.Register(i)        // explicit, no global side effects
s, err := /* parse and compile through i */

To write a new extension, see docs/extensions.md.

Package layout

.
├── sieve.go            top-level façade: Compile, Validate, NewInterpreter, Default
├── ast/                AST node types
├── token/              token types
├── lexer/              source → tokens
├── parser/             tokens → AST
├── registry/           pluggable actions/tests/match-types/capabilities
├── interpreter/        AST walker + RFC 5228 builtins
├── message/            Message interface + net/mail adapter + Builder
├── extensions/
│   ├── fileinto/       RFC 5232
│   ├── envelope/       RFC 5228 §5.4
│   ├── body/           RFC 5173
│   ├── imap4flags/     RFC 5232
│   ├── variables/      RFC 5229
│   ├── regex/          draft-ietf-sieve-regex
│   ├── mime/           RFC 5703 (`:mime`, `:anychild`, `foreverypart`, …)
│   ├── reject/         RFC 5429
│   ├── editheader/     RFC 5293
│   ├── vacation/       RFC 5230
│   ├── subaddress/     RFC 5233
│   └── relational/     RFC 5231
├── examples/
│   ├── simple/         embed the library
│   └── validate/       stdin → ok / error CLI
├── docs/
│   └── extensions.md   how to write an extension
└── AGENTS.md           guidance for AI coding agents

Limitations

Known unimplemented pieces — each one fails loudly (validation error, runtime error, or simply doesn't fire) rather than silently mismatching:

  • include (RFC 6609) — needs a host-supplied script loader callback to resolve :personal / :global scripts. Left as a follow-up so the design can land alongside a concrete embedding.
  • notify (RFC 5435) — requires per-method URI dispatch (xmpp:, mailto:, sms:, …). Same reasoning: best shipped with a real host binding rather than a placeholder.
  • environment (RFC 5183), date / currentdate (RFC 5260), spamtest / virustest (RFC 5235), and a handful of niche IANA capabilities — not yet built. The registry is the extension point; see docs/extensions.md and docs/ai-implementation-guide.md.

These are good first contributions if you want to dig in.

References

License

MIT

Documentation

Overview

Package sieve is the top-level façade for the Sieve interpreter.

Typical use:

script, err := sieve.Compile(src)
if err != nil { /* syntax/validation error */ }
if err := script.Run(msg, handler); err != nil { /* runtime error */ }

For one-off validation use Validate. To register additional extensions, use NewInterpreter and access its Registry, then Compile through it.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Validate

func Validate(src string) error

Validate parses and validates src without keeping the compiled script.

Types

type Handler

type Handler = registry.Handler

Handler is the host application's action receiver. Extensions that define additional actions (e.g. fileinto) define richer interfaces in their own packages and type-assert this one.

type Interpreter

type Interpreter = interpreter.Interpreter

Interpreter wraps the registry and core builtins.

func Default

func Default() *Interpreter

Default returns the package-level interpreter used by Compile and Validate. Extension packages call this from their init() to self-register.

func NewInterpreter

func NewInterpreter() *Interpreter

NewInterpreter returns a fresh Interpreter with RFC 5228 builtins registered. Extensions can be added via i.Registry().

type Message

type Message = message.Message

Message is the abstract message view consulted by tests.

type Script

type Script = interpreter.Script

Script is a compiled, validated Sieve script.

func Compile

func Compile(src string) (*Script, error)

Compile parses and validates src using the default interpreter.

Directories

Path Synopsis
Package ast defines the Sieve abstract syntax tree.
Package ast defines the Sieve abstract syntax tree.
examples
attachments command
Demonstrates RFC 5703 "mime" extension: filing any message with an attachment into a dedicated folder.
Demonstrates RFC 5703 "mime" extension: filing any message with an attachment into a dedicated folder.
simple command
Command simple shows how a host application embeds the sieve library: parse a script, supply a message, and implement a Handler.
Command simple shows how a host application embeds the sieve library: parse a script, supply a message, and implement a Handler.
validate command
Command validate checks a Sieve script for syntax and semantic errors without running it.
Command validate checks a Sieve script for syntax and semantic errors without running it.
extensions
body
Package body implements the Sieve "body" extension (RFC 5173).
Package body implements the Sieve "body" extension (RFC 5173).
editheader
Package editheader implements the Sieve "editheader" extension (RFC 5293).
Package editheader implements the Sieve "editheader" extension (RFC 5293).
envelope
Package envelope implements the Sieve "envelope" extension (RFC 5228 §5.4).
Package envelope implements the Sieve "envelope" extension (RFC 5228 §5.4).
fileinto
Package fileinto implements the Sieve "fileinto" extension (RFC 5232).
Package fileinto implements the Sieve "fileinto" extension (RFC 5232).
imap4flags
Package imap4flags implements the Sieve "imap4flags" extension (RFC 5232): the actions `setflag`, `addflag`, `removeflag` and the test `hasflag`.
Package imap4flags implements the Sieve "imap4flags" extension (RFC 5232): the actions `setflag`, `addflag`, `removeflag` and the test `hasflag`.
mime
Package mime implements the subset of the Sieve "mime" extension (RFC 5703) that scripts most commonly need: the ":mime" and ":anychild" tags on the existing "header", "address", and "exists" tests.
Package mime implements the subset of the Sieve "mime" extension (RFC 5703) that scripts most commonly need: the ":mime" and ":anychild" tags on the existing "header", "address", and "exists" tests.
regex
Package regex implements the Sieve ":regex" match type from draft-ietf-sieve-regex (the "regex" capability).
Package regex implements the Sieve ":regex" match type from draft-ietf-sieve-regex (the "regex" capability).
reject
Package reject implements the Sieve "reject" and "ereject" actions (RFC 5429).
Package reject implements the Sieve "reject" and "ereject" actions (RFC 5429).
relational
Package relational implements the Sieve "relational" extension (RFC 5231).
Package relational implements the Sieve "relational" extension (RFC 5231).
subaddress
Package subaddress implements the Sieve "subaddress" extension (RFC 5233).
Package subaddress implements the Sieve "subaddress" extension (RFC 5233).
vacation
Package vacation implements the Sieve "vacation" action (RFC 5230).
Package vacation implements the Sieve "vacation" action (RFC 5230).
variables
Package variables implements the Sieve "variables" extension (RFC 5229).
Package variables implements the Sieve "variables" extension (RFC 5229).
Package interpreter wires the AST, registry, message, and host Handler together.
Package interpreter wires the AST, registry, message, and host Handler together.
Package lexer turns Sieve source text into a stream of tokens.
Package lexer turns Sieve source text into a stream of tokens.
Package message defines the abstract view of an email message that Sieve tests and actions consult.
Package message defines the abstract view of an email message that Sieve tests and actions consult.
Package parser turns a token stream from package lexer into an ast.Script.
Package parser turns a token stream from package lexer into an ast.Script.
Package registry holds the set of Sieve commands, tests, and tagged arguments that an interpreter knows about.
Package registry holds the set of Sieve commands, tests, and tagged arguments that an interpreter knows about.
Package token defines the lexical tokens produced by the Sieve lexer.
Package token defines the lexical tokens produced by the Sieve lexer.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL