rules

package
v0.10.1 Latest Latest
Warning

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

Go to latest
Published: Jun 24, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package rules provides a CEL-based message rules engine for the mailbox library.

Rules evaluate Common Expression Language (CEL) conditions against incoming or outgoing messages and apply actions such as moving to a folder, adding tags, marking as read, or suppressing delivery.

The engine is designed as a mailbox Plugin (implementing SendHook for send-side rules) and an event handler (OnMessageReceived for receive-side rules). This keeps the core mailbox library clean and makes rules fully optional.

Rule Scopes

Rules have two scopes:

  • ScopeReceive: evaluated when a message is delivered to a recipient. All actions are valid (SetFolder, AddTag, RemoveTag, MarkRead, Delete, HardDelete, Archive, Spam, Webhook, Forward).
  • ScopeSend: evaluated after the sender's copy is created. Only SetFolder, AddTag, RemoveTag, Archive, and Webhook are valid; other actions are silently skipped.

CEL Variables

The following variables are available in CEL expressions:

sender           string              - sender user ID
subject          string              - message subject
body             string              - message body
recipients       list(string)        - recipient user IDs
headers          map(string, string) - message headers
metadata         map(string, dyn)    - message metadata
has_attachments  bool                - whether the message has attachments
attachment_count int                 - number of attachments
thread_id        string              - thread ID (empty if not a reply)
is_reply         bool                - true if the message is a reply
folder           string              - current folder ID
tags             list(string)        - current tags

Rule Evaluation

All matching rules are applied in priority order (lower value = higher priority). Actions accumulate: if multiple rules set the folder, the last one wins. A rule with StopOnMatch=true stops further rule evaluation for that message.

Integration

Wire the engine as a plugin and event subscriber:

engine, err := rules.NewEngine(provider, st)
svc, err := mailbox.New(mailbox.Config{},
    mailbox.WithStore(st),
    mailbox.WithPlugin(engine),   // registers AfterSend for send-side rules
)
svc.Connect(ctx)

// Subscribe receive-side rules via an adapter (rules can't import mailbox directly):
svc.Events().MessageReceived.Subscribe(ctx,
    func(ctx context.Context, ev event.Event[mailbox.MessageReceivedEvent], data mailbox.MessageReceivedEvent) error {
        return engine.OnMessageReceived(ctx, nil, rules.MessageReceivedData{
            MessageID:   data.MessageID,
            RecipientID: data.RecipientID,
        })
    },
    event.AsWorker("rules-engine"),
)

Index

Constants

View Source
const DefaultMaxCacheSize = 1000

DefaultMaxCacheSize is the default maximum number of compiled CEL programs to cache.

Variables

This section is empty.

Functions

This section is empty.

Types

type Action

type Action struct {
	// Type is the action to perform.
	Type ActionType
	// Value is the action parameter. Semantics depend on Type:
	//   SetFolder  — destination folder ID
	//   AddTag     — tag ID to add
	//   RemoveTag  — tag ID to remove
	//   Webhook    — URL to POST the message payload to
	//   Forward    — target user ID to forward the message to
	//   All others — ignored
	Value string
}

Action defines what to do when a rule matches.

type ActionType

type ActionType string

ActionType defines the type of action to perform when a rule matches.

const (
	// ActionSetFolder moves the message to a specific folder.
	ActionSetFolder ActionType = "set_folder"
	// ActionAddTag adds a tag to the message.
	ActionAddTag ActionType = "add_tag"
	// ActionRemoveTag removes a tag from the message. Value is the tag ID.
	ActionRemoveTag ActionType = "remove_tag"
	// ActionMarkRead marks the message as read. Only valid for receive scope.
	ActionMarkRead ActionType = "mark_read"
	// ActionDelete soft-deletes the message (moves to trash). Only valid for receive scope.
	ActionDelete ActionType = "delete"
	// ActionHardDelete permanently removes the message. Only valid for receive scope.
	// Use with caution -- hard-deleted messages cannot be recovered.
	ActionHardDelete ActionType = "hard_delete"
	// ActionArchive moves the message to the archived folder.
	ActionArchive ActionType = "archive"
	// ActionSpam moves the message to the spam folder. Only valid for receive scope.
	ActionSpam ActionType = "spam"
	// ActionWebhook POSTs the message payload as JSON to the URL in Value.
	// Delivery is fire-and-forget; errors are logged but do not abort rule processing.
	ActionWebhook ActionType = "webhook"
	// ActionForward forwards the message to another user. Value is the target userID.
	// Requires a Forwarder configured via WithForwarder.
	ActionForward ActionType = "forward"
)

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine evaluates CEL rules and applies actions via store operations. It implements the mailbox.Plugin and mailbox.SendHook interfaces.

func NewEngine

func NewEngine(provider RuleProvider, st store.Store, opts ...Option) (*Engine, error)

NewEngine creates a rules engine with the given provider and store. The store is used to apply post-creation mutations (MoveToFolder, AddTag, etc.).

func (*Engine) AfterSend

func (e *Engine) AfterSend(ctx context.Context, userID string, msg store.Message) error

AfterSend evaluates send-scope rules against the sender's message copy and applies valid send-side actions (SetFolder, AddTag, Archive).

func (*Engine) BeforeSend

func (e *Engine) BeforeSend(_ context.Context, _ string, _ store.DraftMessage) error

BeforeSend is a no-op. Rules do not block message sending.

func (*Engine) Close

func (e *Engine) Close(_ context.Context) error

Close clears the compiled program cache.

func (*Engine) Init

func (e *Engine) Init(_ context.Context) error

Init initializes the plugin. The CEL environment is created in the constructor.

func (*Engine) Name

func (e *Engine) Name() string

Name returns the plugin identifier.

func (*Engine) OnMessageReceived

func (e *Engine) OnMessageReceived(ctx context.Context, _ any, data MessageReceivedData) error

OnMessageReceived handles MessageReceived events and evaluates receive-scope rules. Register this as an event handler on the service's MessageReceived event.

The handler signature matches event.Handler[mailbox.MessageReceivedEvent]. The second parameter is the event instance; it is unused here.

type Forwarder added in v0.7.6

type Forwarder interface {
	// Forward delivers a copy of messageID to toUserID.
	Forward(ctx context.Context, messageID, toUserID string) error
}

Forwarder forwards a message to another user on behalf of the rules engine. Implement this interface and register it via WithForwarder to enable ActionForward.

type MessageReceivedData

type MessageReceivedData struct {
	MessageID   string
	RecipientID string
}

MessageReceivedData contains the fields needed by OnMessageReceived. This matches the shape of mailbox.MessageReceivedEvent without importing the parent package (avoiding a circular dependency).

type Option

type Option func(*engineOptions)

Option configures the rules Engine.

func WithForwarder added in v0.7.6

func WithForwarder(f Forwarder) Option

WithForwarder sets the forwarder used by ActionForward rules. Without a forwarder, ActionForward is logged and skipped.

func WithHTTPClient added in v0.7.6

func WithHTTPClient(c *http.Client) Option

WithHTTPClient sets the HTTP client used by ActionWebhook rules. Defaults to http.DefaultClient when not set.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the logger for the rules engine.

func WithMaxCacheSize

func WithMaxCacheSize(size int) Option

WithMaxCacheSize sets the maximum number of compiled CEL programs to cache. When the cache is full, all entries are evicted. Defaults to DefaultMaxCacheSize (1000). Set to 0 to disable caching.

func WithStrictMode

func WithStrictMode(strict bool) Option

WithStrictMode makes rule evaluation errors fatal. When enabled, CEL compilation or evaluation errors cause the operation to fail instead of being logged and skipped.

type Rule

type Rule struct {
	// ID uniquely identifies the rule.
	ID string
	// Name is a human-readable label for the rule.
	Name string
	// Scope determines when the rule is evaluated (receive or send).
	// Defaults to ScopeReceive if empty.
	Scope RuleScope
	// Condition is a CEL expression that must evaluate to true for actions to apply.
	Condition string
	// Actions are applied in order when the condition matches.
	Actions []Action
	// Priority controls evaluation order. Lower values are evaluated first.
	Priority int
	// StopOnMatch stops evaluating further rules for this message when true.
	StopOnMatch bool
}

Rule defines a single rule with a CEL condition and actions.

type RuleProvider

type RuleProvider interface {
	// GlobalRules returns rules that apply to all users for the given scope.
	GlobalRules(ctx context.Context, scope RuleScope) ([]Rule, error)
	// UserRules returns rules for a specific user and scope.
	UserRules(ctx context.Context, userID string, scope RuleScope) ([]Rule, error)
}

RuleProvider supplies rules for evaluation. Implementations can be backed by a database, config file, or any other source.

type RuleScope

type RuleScope string

RuleScope determines when a rule is evaluated.

const (
	// ScopeReceive applies the rule when a message is delivered to a recipient.
	ScopeReceive RuleScope = "receive"
	// ScopeSend applies the rule to the sender's copy after sending.
	ScopeSend RuleScope = "send"
)

type StaticRuleProvider

type StaticRuleProvider struct {
	// contains filtered or unexported fields
}

StaticRuleProvider is an in-memory RuleProvider for testing and simple use cases.

func NewStaticProvider

func NewStaticProvider(global []Rule, perUser map[string][]Rule) *StaticRuleProvider

NewStaticProvider creates a StaticRuleProvider with the given global and per-user rules.

func (*StaticRuleProvider) GlobalRules

func (p *StaticRuleProvider) GlobalRules(_ context.Context, scope RuleScope) ([]Rule, error)

GlobalRules returns global rules filtered by scope.

func (*StaticRuleProvider) UserRules

func (p *StaticRuleProvider) UserRules(_ context.Context, userID string, scope RuleScope) ([]Rule, error)

UserRules returns per-user rules filtered by scope.

Jump to

Keyboard shortcuts

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