store

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: AGPL-3.0 Imports: 28 Imported by: 0

Documentation

Overview

Package store owns the database: opening it, migrating it and the repositories over it. Self-host runs on SQLite, one file in the data directory; the hosted build can run on Postgres, where row-level security keeps each tenant's rows to that tenant (MAI-150).

Index

Constants

View Source
const (
	ActionDone   = "done"
	ActionDryRun = "dry_run"
	ActionFailed = "failed"
	ActionUndone = "undone"
)

Action statuses, as stored in actions.status.

View Source
const (
	BatchLive       = "live"
	BatchCleanup    = "cleanup"
	BatchCorrection = "correction"
	BatchUndo       = "undo"

	BatchRunning = "running"
	BatchDone    = "done"
	BatchFailed  = "failed"
	BatchUndone  = "undone"
)

Batch kinds and statuses, as stored in batches.

View Source
const (
	SettingTrashToFolder = "trash_to_folder"
	DefaultTrashToFolder = true
)

SettingTrashToFolder is the settings key of the switch that sends trashed mail to an ordinary folder of MailRules' own instead of the server's Trash, which providers empty on their own. DefaultTrashToFolder is in force until it is set, for a new install and an existing one alike.

View Source
const (
	SettingLeaveOwnMail = "leave_own_mail"
	DefaultLeaveOwnMail = true
)

SettingLeaveOwnMail is the settings key of the switch that leaves mail sent from the mailbox's own address alone (Account.OwnAddresses): no rule, model or action touches it. DefaultLeaveOwnMail is in force until it is set.

View Source
const (
	AuditRuleCreated         = "rule.created"
	AuditRuleChanged         = "rule.changed"
	AuditRuleDeleted         = "rule.deleted"
	AuditRulesReordered      = "rules.reordered"
	AuditSenderRuleSet       = "sender_rule.set"
	AuditSenderRuleDeleted   = "sender_rule.deleted"
	AuditMailboxAdded        = "mailbox.added"
	AuditMailboxChanged      = "mailbox.changed"
	AuditMailboxRemoved      = "mailbox.removed"
	AuditSettingsChanged     = "settings.changed"
	AuditDestinationAdded    = "notification.added"
	AuditDestinationRemoved  = "notification.removed"
	AuditPlanChanged         = "plan.changed"
	AuditMemberJoined        = "member.joined"
	AuditMemberRoleChanged   = "member.role_changed"
	AuditMemberRemoved       = "member.removed"
	AuditInvitationCreated   = "invitation.created"
	AuditInvitationRevoked   = "invitation.revoked"
	AuditInvitationAccepted  = "invitation.accepted"
	AuditInvitationWithdrawn = "invitation.withdrawn"
	// An Owner asked the billing service to add or remove the company sign-in add-on; the
	// change itself is the plan.changed row once the service reports it.
	AuditCompanySignInAddOn = "company_sign_in.add_on"
	// An Owner or Admin opened a link to set company sign-in up at the sign-in service.
	AuditCompanySignInPortal = "company_sign_in.portal"
)

What the audit log records (AuditEntry.Action): who changed the tenant's rules, sender rules, mailboxes, members, invitations, settings and plan, and who asked for the company sign-in add-on or opened its setup link.

View Source
const (
	StateNew     = "new"     // ingested, not yet decided
	StateDecided = "decided" // a decision is recorded, its actions are not finished
	StateActed   = "acted"   // the decision's actions ran (or were recorded as dry-run)
	StateReview  = "review"  // waiting in Needs review
	StateSkipped = "skipped" // nothing to do: no rule matched, or the message was gone
	StateError   = "error"   // failed; the retry job runs it again at NextAttemptAt
)

Message states, as stored in messages.state.

View Source
const (
	CorrectionFix    = "correction"
	CorrectionReview = "review"
)

What a correction was: a fix made from the feed, or an answer given in Needs review.

View Source
const (
	QuestionOpen = "open"
	QuestionYes  = "yes"
	QuestionNo   = "no"
)

Question statuses, as stored in move_questions.status.

View Source
const (
	DestinationWebhook = "webhook"
	DestinationSlack   = "slack"
)

Notification destination kinds, as stored in notify_destinations.kind.

View Source
const (
	DeliverySent   = "sent"
	DeliveryFailed = "failed"
)

Delivery statuses, as stored in notify_deliveries.status.

View Source
const (
	PlanTrial    = "trial" // no plan bought yet, within TrialDays of the tenant's creation
	PlanPersonal = "personal"
	PlanPro      = "pro"
	PlanTeam     = "team"  // sold by the seat (Plan.Seats)
	PlanEnded    = "ended" // the trial or the paid plan is over: nothing is sorted
)

Plans, as a billing module reports them (SetPlan) and as TenantPlan sees them.

View Source
const (
	IntervalMonth = "month"
	IntervalYear  = "year"
)

Billing intervals (Plan.Interval).

View Source
const (
	StatusActive = "active"
	// StatusPastDue: a renewal is unpaid and the billing service retries it; the plan runs.
	StatusPastDue = "past_due"
	// StatusCancelsAtPeriodEnd: cancelled, running to PeriodEnd, when it ends.
	StatusCancelsAtPeriodEnd = "cancels_at_period_end"
)

Plan statuses (Plan.Status).

View Source
const (
	// TrialDays is how long a tenant's trial runs from its creation.
	TrialDays = 14
	// EndedSecretsDays is how many days after a plan ends the tenant's mailbox passwords and
	// tokens and its model keys are deleted.
	EndedSecretsDays = 30
	// EndedRemovalDays is how many days after a plan ends everything else of the tenant is
	// removed.
	EndedRemovalDays = 90
)
View Source
const (
	SummarySending = "sending"
	SummarySent    = "sent"
	SummaryFailed  = "failed"
)

Summary statuses, as stored in summaries.status.

View Source
const (
	// RoleOwner: billing and the plan, members, everything an admin does.
	RoleOwner = "owner"
	// RoleAdmin: mailboxes, the shared rules and sender rules, members, settings, the audit log.
	RoleAdmin = "admin"
	// RoleMember: their own mailboxes, sorted by the shared rules.
	RoleMember = "member"
)

The roles a user has in their tenant (User.Role). Who may do what with each is the API's to enforce; the store only keeps them, and keeps a team from losing its last owner.

View Source
const ActionDraft = "draft"

ActionDraft is the kind of the action that saves a reply a rule's drafting module wrote in the mailbox's Drafts folder. It changes nothing of the email it answers: its Before and After are where that email was, and Draft is where the reply went.

View Source
const ActionInterrupted = "interrupted before it finished"

ActionInterrupted is the error a row is written with before its change is made, which stays only when the daemon stopped before it recorded how the change ended.

View Source
const AuditDays = 365

AuditDays is how long a tenant's audit log keeps a row: a year, then the retention job deletes it.

View Source
const DefaultTenantKeyCache = time.Hour

DefaultTenantKeyCache is how long an unwrapped tenant key stays in memory unless the Keyring is told otherwise (MAILRULES_TENANT_KEY_CACHE).

View Source
const DraftWait = day

DraftWait is how long a draft job is tried, in seconds from when its rule acted: one not written by then is given up and recorded as a failed draft action. A reply drafted a day late is of little use, and the email may be answered by then.

View Source
const ForgetDays = 30

ForgetDays is how long what MailRules kept of a forgotten person waits before it is removed. Signing in again with the same identity before then cancels the removal, unless the person deleted their own account (DeleteOwnAccount) or was removed from their team (RemoveMember).

View Source
const InvitationDays = 7

InvitationDays is how long an invitation waits to be accepted before it expires; once the sign-in service says it was accepted, it waits as long again for the person's first sign-in.

View Source
const KeepMessagesDays = 180

KeepMessagesDays is how long a message's row is kept. After that it is deleted with its decisions and actions, unless one of its actions can still be undone: a move or a flag change that is in effect and was made in the last UndoDays. A recorded "keep" changed nothing worth putting back, so it does not hold a row.

View Source
const MoveDays = 30

MoveDays is how long a move the watcher found is kept: what the module was told, and what a question counts.

View Source
const NewAccountShared = false

NewAccountShared is whether a mailbox someone adds starts shared with everyone in their tenant. It starts private: only its owner sees it, its mail and what MailRules did to it, until the owner shares it.

View Source
const NoLimit = -1

NoLimit is a limit that does not limit (CreateAccountWithin, and the limits a module sets).

View Source
const NoticeDays = 30

NoticeDays is how long a notice, and what its module reported of delivering it, is kept.

View Source
const RemovedMailboxDays = ForgetDays

RemovedMailboxDays is how long a removed mailbox waits for the module that connected it to take it (ext.Module.MailboxRemoved); then it is deleted with its copy of the secret.

View Source
const SelfHostTenant int64 = 1

SelfHostTenant is the one tenant of a self-hosted install, made by migration 0011.

View Source
const SettingSummary = "summary"

SettingSummary holds the summary email's settings, one JSON object (internal/summary).

View Source
const UndoDays = 30

UndoDays is how long an action stays undoable. Older ones are history: undo refuses them (actions.ErrTooOld) and they no longer hold their message's row.

Variables

View Source
var (
	// ErrTenantMismatch is a returning identity that arrived under a different organisation
	// than the one it signed up with.
	ErrTenantMismatch = errors.New("identity belongs to another tenant")
	// ErrEmailInUse is an email another user already has.
	ErrEmailInUse = errors.New("email in use by another user")
	// ErrRemovedMember is a person removed from a team (RemoveMember) signing in under its
	// organisation again without a new invitation.
	ErrRemovedMember = errors.New("removed from the team: a new invitation is needed")
)
View Source
var (
	// ErrLastOwner is a change that would leave a team with no owner.
	ErrLastOwner = errors.New("the team's last owner")
	// ErrBadRole is a role that is not one of Roles.
	ErrBadRole = errors.New("unknown role")
	// ErrAlreadyMember is an invitation for someone who is in the team already.
	ErrAlreadyMember = errors.New("already a member of the team")
	// ErrAlreadyInvited is an invitation for an address that has one waiting.
	ErrAlreadyInvited = errors.New("already invited")
	// ErrHasAccount is an invitation for an address that has a MailRules account of its own,
	// in any tenant: one person is one user in one tenant.
	ErrHasAccount = errors.New("the address has a MailRules account")
	// ErrSeatLimit is what a *SeatLimitError matches with errors.Is.
	ErrSeatLimit = errors.New("seat limit reached")
)
View Source
var ErrAlreadySetUp = errors.New("already set up")

ErrAlreadySetUp means the first-run admin account already exists.

View Source
var ErrAnswered = errors.New("the person already answered the question about this sender and folder")

ErrAnswered is what AskMove returns when the person already answered the question about that sender and destination. Nothing changed.

View Source
var ErrBadPlan = errors.New("unknown plan, interval or status")

ErrBadPlan is what SetPlan returns for a plan, interval, status or seat count it does not know.

View Source
var ErrMailboxLimit = errors.New("the mailbox limit is reached")

ErrMailboxLimit is what CreateAccountWithin returns, as a *MailboxLimitError, when the tenant already has as many mailboxes as it may.

View Source
var ErrNoKeys = errors.New("store: no key provider")

ErrNoKeys means the store was given no KeyProvider, so it can seal or open no secret.

View Source
var ErrNoSecret = errors.New("the mailbox's password was deleted; enter it again")

ErrNoSecret is the answer for a mailbox whose secret was deleted (Account.SecretGone).

View Source
var ErrNotFound = errors.New("not found")

ErrNotFound means the row does not exist (or, for a session, has expired).

View Source
var ErrPlanNotEnded = errors.New("the tenant's plan has not ended")

ErrPlanNotEnded is what the end-of-plan steps return, changing nothing, for a tenant whose plan no longer ended when the caller saw it end: one bought again meanwhile, or one that ended again at another time.

View Source
var ErrRuleSet = errors.New("the list must name every rule exactly once")

ErrRuleSet means a reorder did not name each of the tenant's rules exactly once.

View Source
var ErrSchema = errors.New("the database schema does not match this build")

ErrSchema means the database's schema is not the one this build was made for: migrations it has not run, or one from a newer build.

View Source
var ErrTenantScope = errors.New("store: a tenant outside the current scope")

ErrTenantScope means a method was asked for one tenant inside the scope of another.

View Source
var Outcomes = []string{"sorted", "trashed", "inbox", "review"}

Outcomes are the four places a processed message ends up: the groups of the Overview bar (Totals.Went*) and the values of ActivityFilter.Outcome.

Roles are the roles, from the most a user may do to the least.

Functions

func CheckSchema added in v0.3.0

func CheckSchema(ctx context.Context, db *sql.DB) error

CheckSchema reports, without changing anything, whether every embedded migration of the database's dialect has run, and none it does not know: what a daemon on Postgres checks at start, since it never migrates there (the roles it runs as own no table).

func Migrate

func Migrate(ctx context.Context, db *sql.DB) error

Migrate applies every embedded migration of the database's dialect that has not run yet. On Postgres it runs as the role that owns the tables, and holds a session lock so two processes starting at once do not both migrate.

func Month added in v0.3.0

func Month(t time.Time) string

Month is the calendar month, in UTC as the usage ledger's days are, that an email decided at t counts in: YYYY-MM.

func Open

func Open(ctx context.Context, dataDir string) (*sql.DB, error)

Open opens (creating if needed) the SQLite database in dataDir, in WAL mode. Every transaction not begun read-only begins IMMEDIATE, holding the write lock from its first statement: a deferred one that read first could not write once another connection had committed meanwhile (SQLITE_BUSY_SNAPSHOT), which mailbox supervisors do all the time. Begin one that only reads with sql.TxOptions{ReadOnly: true}, so that it waits for no writer.

func OpenPostgres added in v0.3.0

func OpenPostgres(ctx context.Context, dsn string) (*sql.DB, error)

OpenPostgres connects to the Postgres database at dsn (a postgres:// URL or key=value string). The role it signs in as must not own the tables nor bypass row-level security: the store relies on the policies to keep tenants apart. Migrations run as the owner.

func OpenSetting added in v0.3.0

func OpenSetting(k crypto.TenantKey, name, stored string) (string, error)

OpenSetting opens a value SealSetting stored for name with the tenant's key k, or one sealed before tenant keys. A wrong key is crypto.ErrDecrypt.

func RoleName added in v0.3.0

func RoleName(role string) string

RoleName is a role as people read it: "Owner".

func RoleRank added in v0.3.0

func RoleRank(role string) int

RoleRank orders the roles: an owner ranks above an admin, an admin above a member. An unknown role ranks below them all.

func SealSetting added in v0.3.0

func SealSetting(k crypto.TenantKey, name, value string) (string, error)

SealSetting seals value for the settings row of name under the tenant's key k, as the row's JSON value.

func TenantOf added in v0.3.0

func TenantOf(ctx context.Context) int64

TenantOf is the tenant ctx is scoped to, 0 for none.

func URLHint added in v0.3.0

func URLHint(raw string) string

URLHint is an address as Settings shows it: its scheme and host, then the last four characters when it has a path or query, so a Slack webhook's token stays unread.

func WithActor added in v0.3.0

func WithActor(ctx context.Context, a Actor) context.Context

WithActor says who makes the changes the store is asked for with ctx: each change to what the audit log covers is then recorded under them, in its own transaction. A request carries the signed-in person; a module's call carries the service it speaks for. Changes made with no actor (the mailbox workers, the startup checks, the retention job) are not recorded.

func WithTenant added in v0.3.0

func WithTenant(ctx context.Context, tenantID int64) context.Context

WithTenant scopes ctx to the tenant: everything the store runs with it reads and writes that tenant's rows only. Inside a scope for another tenant, statements run with the result fail with ErrTenantScope. Tenant 0 adds nothing.

func WithoutActor added in v0.3.0

func WithoutActor(ctx context.Context) context.Context

WithoutActor is ctx with no actor: what is changed with it is not recorded, such as a token a provider rotated in the middle of a request.

Types

type Account

type Account struct {
	ID           int64
	UserID       int64 // the owner: who added it
	TenantID     int64 // the owner's tenant
	Shared       bool  // everyone in the tenant sees it; otherwise only the owner
	Label        string
	Preset       string
	Host         string
	Port         int
	TLSMode      string
	Username     string
	WatchFolder  string
	Status       string // new | live | reconnecting | auth_failed | reconnect_needed | cert_changed | error | paused
	LastError    string
	LastEventAt  int64
	Capabilities []string
	CreatedAt    int64
	// CertFingerprint is the server certificate the person accepted, as mail.Cert has it:
	// the server must present exactly that one. Empty: the system's trust store decides.
	CertFingerprint string
	// OAuth marks a mailbox connected with one-click sign-in: the module that signs in to its
	// preset turns the stored secret (a refresh token) into an access token before every login.
	OAuth bool
	// Module marks a mailbox a module added (ext.Host.AddMailbox): its server is the
	// module's, so the hosted build connects to it even when it is not on the public
	// internet (publicnet). Moving the mailbox to another server clears it.
	Module bool
	// SecretGone: the mailbox's password or token was deleted (ForgetIdentity), so nothing
	// can log in to it and no supervisor is started for it until its owner enters a new one.
	SecretGone bool
}

Account is one mailbox the daemon watches. Its password is never part of this struct: read it with AccountSecret at the moment a connection is made.

func (Account) OwnAddresses

func (a Account) OwnAddresses() []string

OwnAddresses returns the account's own email addresses, lowercase: the username when it is a full address and, for a provider whose domains are known (presets.Preset.Domains), the name before "@" at each of them, so an iCloud username of only "jane" is jane@icloud.com, jane@me.com and jane@mac.com. Aliases are not known. It is nil when no address can be told: a username without "@" on a provider whose domains are not known.

func (Account) Owner added in v0.2.0

func (a Account) Owner() Viewer

Owner is the account's owner as a viewer: who internal work on one account (the pipeline, the learner) acts for.

type Action

type Action struct {
	ID         int64
	DecisionID int64 // 0 = none
	BatchID    int64 // 0 = none
	MessageID  int64
	AccountID  int64
	Kind       string // move | trash | archive | junk | flag | unflag | read | unread | keep | review | draft
	Folder     string // params.folder: the destination of a move, of a trash sent to MailRules' own folder (trash_to_folder), or the Drafts folder of a draft
	// Draft is where a draft action saved its reply, once it did (params.draft); nil for
	// every other kind.
	Draft     *DraftRef
	Before    Snapshot
	After     *Snapshot // nil unless the action is done (or was, before an undo)
	Status    string
	Error     string
	CreatedAt int64
	UndoneAt  int64
}

Action is one row of actions: one step taken (or, in dry-run, not taken) on a message.

type ActivityFilter

type ActivityFilter struct {
	ID        int64 // only this message
	AccountID int64
	RuleID    int64
	Stage     string
	State     string // a message state; StateReview lists Needs review
	Action    string // an action kind: only messages with such an action
	Outcome   string // one of Outcomes: where the message ended up, as the stats count it
	After     FeedCursor
	Limit     int // 0 = 50
}

ActivityFilter narrows Activity. Zero fields do not filter.

type ActivityRow

type ActivityRow struct {
	Message  Message
	Decision *Decision
	Actions  []Action
}

ActivityRow is a message with its latest decision and every action taken on it, oldest first; Decision is nil until one is made.

func (ActivityRow) Cursor

func (r ActivityRow) Cursor() FeedCursor

Cursor is the position of this row in the feed, for the page after it.

type Actor added in v0.3.0

type Actor struct {
	UserID int64
	Name   string
}

Actor is who makes the changes of a context (WithActor): a person, by their user and email, or a service by its name (UserID 0).

func ActorOf added in v0.3.0

func ActorOf(ctx context.Context) (Actor, bool)

ActorOf is the actor of ctx; ok is false when it has none.

type AuditEntry added in v0.3.0

type AuditEntry struct {
	ID      int64
	At      int64
	ActorID int64 // 0: a service, or a person whose user is gone
	Actor   string
	Action  string // one of the Audit* constants
	Target  string // what was changed, by name: a rule, a mailbox's address, a member's email
	Detail  string // how, in a few words; never an address (withoutAddresses)
}

AuditEntry is one row of a tenant's audit log. It never holds email content or a secret.

type Batch

type Batch struct {
	ID        int64
	TenantID  int64
	Kind      string // live | cleanup | review | correction | undo
	Status    string // running | done | failed | undone
	Total     int    // how many items the batch set out to handle; 0 = not counted (live)
	Done      int    // how many it has handled
	CreatedAt int64

	// What a cleanup batch sorted, and what its model calls have cost so far. Zero for
	// the other kinds.
	AccountID int64
	Folder    string
	Since     int64 // only mail received from this time on; 0 = all of it
	Tokens    int   // model tokens, in and out
	CostUSD   float64
	// Skipped is how many selected emails a cleanup Sort passed over because they were no
	// longer where its check found them (MAI-44). 0 for the other kinds.
	Skipped int
	// ScanLimit is the most emails the cleanup's check was allowed to cover, and ScanMatched
	// how many the folder held in the range before that cut (MAI-48). ScanLimit 0 = not
	// recorded (a batch made before this), and then ScanMatched means nothing.
	ScanLimit   int
	ScanMatched int
	// RunID joins the batches of one manual run over several mailboxes, one per mailbox
	// (MAI-43): it is the id of the run's first batch, the same on every one. 0 = not part of
	// such a run, which is every other batch and a cleanup of one mailbox.
	RunID int64
}

Batch is one row of batches: a group of actions that can be undone together.

type CleanupBatch added in v0.1.2

type CleanupBatch struct {
	AccountID int64
	Folder    string
	Since     int64 // only mail received from this time on; 0 = all of it
	// ScanLimit and ScanMatched say how much mail the check behind the batch covered: the
	// most emails it was allowed and how many the range held before that cut. ScanLimit 0
	// records nothing.
	ScanLimit, ScanMatched int
	Total                  int // emails the batch sorts
}

CleanupBatch is what a cleanup batch is opened with.

type Contact

type Contact struct {
	Address    string // lower-case
	LastSentAt int64
}

Contact is an address the account's owner has sent mail to.

type Correction

type Correction struct {
	ID          int64
	MessageID   int64
	WrongRuleID int64  // 0 = no rule had been applied
	RightRuleID int64  // 0 = keep in the inbox
	RightRule   string // that rule's name, when read back; empty for keep, or once the rule is deleted
	Kind        string // CorrectionFix | CorrectionReview; empty is stored as CorrectionFix
	Example     string // JSON message.Summary, the few-shot example
	CreatedAt   int64
}

Correction is one row of corrections: the user said a message belongs to another rule.

type Decision

type Decision struct {
	ID          int64
	MessageID   int64
	Stage       string // sender | condition | decider | fallback | none
	RuleID      int64  // 0 = no rule; in Needs review, the rule the model suggested
	RuleName    string // the rule's name: as it is now, or as it was when decided if the rule is gone
	RuleVersion int
	Confidence  float64
	Reason      string
	Model       string
	TokensIn    int
	TokensOut   int
	CostUSD     float64
	LatencyMS   int64
	CreatedAt   int64
	// Probabilities is what the decision model gave each candidate rule, by rule id, with
	// 0 for "none of these". nil when the model gives no such spread, or none was asked.
	Probabilities map[int64]float64
	// SenderSource is who made the sender rule that settled the email (Stage sender): user
	// or learned. Empty for other stages, and for a sender decision recorded before it was
	// kept whose origin cannot be told.
	SenderSource string
	// Metered: a model on keys the operator holds made the decision, so AddDecision counts the
	// email against its tenant's monthly allowance (once per message). It is not stored on
	// the decision.
	Metered bool
}

Decision is one row of decisions: what was decided for a message, and what it cost.

type Delivery added in v0.3.0

type Delivery struct {
	ID            int64
	DestinationID int64
	NoticeID      int64
	RuleID        int64 // 0 once the rule was deleted
	RuleName      string
	Status        string // DeliverySent | DeliveryFailed
	Reason        string // why it failed; empty when sent
	CreatedAt     int64
}

Delivery is one result a module reported of delivering a notice.

type Destination added in v0.3.0

type Destination struct {
	ID        int64
	TenantID  int64
	Name      string
	Kind      string // DestinationWebhook | DestinationSlack
	URLHint   string // the address cut short (URLHint), the only form of it shown
	CreatedAt int64
	// AddressDeleted: the address and secret were deleted with the sign-in of the person who
	// was the tenant's only member (ForgetIdentity); nothing can be delivered to it.
	AddressDeleted bool
}

Destination is one place a tenant's rule notifications go. Its address and signing secret are never part of it: read them with DestinationSecret when a notice is handed over.

func (Destination) NeedsHTTPS added in v0.3.0

func (d Destination) NeedsHTTPS() bool

NeedsHTTPS reports a destination added with a plain http address before only https was taken: it stays listed, but no notice is queued or handed over for it until it is replaced.

type DestinationSecret added in v0.3.0

type DestinationSecret struct {
	URL    string `json:"url"`
	Secret string `json:"secret,omitempty"`
}

DestinationSecret is what is sealed for a destination: where notices go and, for a webhook, the secret deliveries are signed with.

type DraftJob added in v0.3.0

type DraftJob struct {
	ID         int64
	TenantID   int64
	AccountID  int64
	MessageID  int64 // the Activity id
	DecisionID int64
	BatchID    int64
	RuleID     int64 // 0 once the rule was deleted
	RuleName   string
	// Instruction and ReadPrevious are the rule's Draft as it stood when it acted.
	Instruction  string
	ReadPrevious bool
	// Key is the job's own, the same on every attempt: the draft's Message-ID is made from
	// it, so a draft saved before a crash is found again instead of saved twice.
	Key       string
	CreatedAt int64 // when the rule acted
	Attempts  int   // tries that failed so far
}

DraftJob is one reply waiting to be drafted: a rule with Draft set acted on an email live.

type DraftRef added in v0.3.0

type DraftRef struct {
	UIDValidity uint32 `json:"uidvalidity"`
	UID         uint32 `json:"uid"`
	// MessageID is the reply's own Message-ID, without angle brackets: how it is found
	// again when its folder's UIDVALIDITY changed, and checked before it is deleted.
	MessageID string `json:"message_id"`
}

DraftRef is where a draft action saved its reply: params.draft.

type EndedPlan added in v0.3.0

type EndedPlan struct {
	PlanState
	// WarnedAt is when warning number Warned was sent (PlanWarned); 0 while none was.
	WarnedAt int64
}

EndedPlan is a tenant's plan as the end-of-plan job sees it: TenantPlan's state, and when the latest warning email was sent.

type FeedCursor

type FeedCursor struct {
	ActedAt int64
	ID      int64
}

FeedCursor is where a page of Activity ends: the ActedAt and ID of its last row. The next page starts after it in the feed's order. The zero value is the first page.

type Filed added in v0.3.0

type Filed struct {
	ActionID  int64
	MessageID int64
	MsgID     string // the RFC 5322 Message-ID, without angle brackets
	From      string
	RuleID    int64  // the rule that decided; 0 = none, or deleted since
	RuleName  string // its name when it decided
	Before    Snapshot
	After     Snapshot
	FiledAt   int64
}

Filed is an email a done move-type action of MailRules left somewhere and that no later MailRules action moved on: where the move watcher expects it.

type Folder

type Folder struct {
	AccountID   int64
	Name        string
	Delimiter   string
	SpecialUse  string // \Junk \Trash \Archive \Sent \Drafts or empty
	UIDValidity uint32
	LastUID     uint32 // highest UID processed
}

Folder is one folder of an account, with the watch position when it is watched.

type FollowUps added in v0.3.0

type FollowUps struct {
	Notices bool // the rule's notifications (queueNotices)
	Draft   bool // the rule's draft reply (queueDraft)
}

FollowUps are what a rule's run on one email starts once the action that ends it is recorded, in the same transaction, so they exist exactly when the actions they follow do.

type Forget added in v0.3.0

type Forget struct {
	UserID, TenantID int64 // 0 when no user has the identity
	// Accounts are the person's mailboxes whose secrets it deleted: every mailbox of the
	// tenant when they are its only member, else the ones they own, shared or not.
	Accounts []int64
	// Keys is whether it deleted the tenant's stored model API keys, and the addresses and
	// signing secrets of its notification destinations: the person is the tenant's only
	// member.
	Keys bool
}

Forget is what ForgetIdentity did at once.

type Forgotten added in v0.3.0

type Forgotten struct {
	UserID, TenantID int64
	RemoveAt         int64
}

Forgotten is a forgotten user whose removal is waiting.

type Identity added in v0.2.0

type Identity struct {
	Provider, Subject, Email, Tenant string
}

Identity is a person as a sign-in service names them: Subject is the service's stable id for them and Tenant the id of the organisation they signed in under.

type Invitation added in v0.3.0

type Invitation struct {
	ID, TenantID int64
	Email        string
	Role         string
	// InvitedBy is the user who sent it, and InvitedByEmail their address; 0 and empty once
	// they left.
	InvitedBy      int64
	InvitedByEmail string
	// Record is the module's own reference to it at the service that sent it.
	Record     string
	CreatedAt  int64
	ExpiresAt  int64
	AcceptedAt int64 // 0 until the sign-in service says it was accepted
}

Invitation is an invitation to join a tenant, waiting.

type Keyring added in v0.3.0

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

Keyring is a KeyProvider and the tenant keys it has unwrapped. Each stays in memory for a bounded time, then is unwrapped again, so a tenant whose key the provider no longer unwraps (a key service that went away) can open nothing once that time is up. Keys are held by tenant, KEK and wrapped key: one that changes (a new tenant under a reused id, a rotation) is unwrapped anew. Requests that need the same key at once share one unwrap. Every Store that seals with the same provider should share one Keyring (the app's and the worker's connections), so a tenant's key is unwrapped once for both.

func NewKeyring added in v0.3.0

func NewKeyring(p crypto.KeyProvider, ttl time.Duration) *Keyring

NewKeyring is a Keyring for p that keeps a key unwrapped for ttl (DefaultTenantKeyCache when ttl is not positive).

type Ledger added in v0.2.0

type Ledger struct {
	Store    *Store
	TenantID int64
	Operator func(provider string) bool
}

Ledger books model usage for one tenant (models.UsageStore). Operator says, by the provider a call went to (models.Usage.Provider), whether it used a key the operator holds; nil marks none.

func (Ledger) AddUsage added in v0.2.0

func (l Ledger) AddUsage(ctx context.Context, day, provider, model, purpose string, calls, tokensIn, tokensOut int, costUSD float64) error

AddUsage books calls for the ledger's tenant.

type MailboxLimitError added in v0.3.0

type MailboxLimitError struct {
	Limit, Connected int
}

MailboxLimitError is a mailbox refused because its tenant has Connected mailboxes and may have Limit. Its text is the sentence the screen shows.

func (*MailboxLimitError) Error added in v0.3.0

func (e *MailboxLimitError) Error() string

func (*MailboxLimitError) Is added in v0.3.0

func (e *MailboxLimitError) Is(target error) bool

Is makes errors.Is(err, ErrMailboxLimit) true.

type MasterKeyUses added in v0.3.0

type MasterKeyUses struct {
	TenantKeys, Secrets, RemovedMailboxes int
}

MasterKeyUses counts what only the master key opens: tenant keys wrapped under it (kek_id "master"), secrets sealed under it before tenant keys (envelope 1), and removed mailboxes' copies sealed under it (envelope 1, or envelope 3 under "master"). In a build whose module holds the keys, the master key can be retired once all are 0.

func (MasterKeyUses) Total added in v0.3.0

func (u MasterKeyUses) Total() int

Total is everything that still needs the master key.

type Member added in v0.3.0

type Member struct {
	UserID   int64
	Email    string
	Role     string
	JoinedAt int64
}

Member is a person of a tenant, as the Members screen lists them. Those forgotten (ForgetIdentity), whose removal waits, are not members.

type Message

type Message struct {
	ID            int64
	AccountID     int64
	Folder        string // where it arrived; with UIDValidity and UID, the dedupe key
	UIDValidity   uint32
	UID           uint32
	MessageID     string // RFC 5322 Message-ID, without angle brackets
	FromAddr      string
	FromName      string // the From display name; may be empty
	FromDomain    string
	ToAddrs       []string
	Subject       string
	Snippet       string
	ReceivedAt    int64
	ListID        string
	HasAttachment bool
	Size          int64
	Signals       Signals
	// UnsubscribeURL is the RFC 8058 one-click link the email offers (unsubscribe.OneClick);
	// empty when it offers none.
	UnsubscribeURL string
	State          string
	Attempts       int   // retries made after a failure
	NextAttemptAt  int64 // 0 = not waiting for the retry job
	CreatedAt      int64
	// ActedAt is when MailRules last decided the message, or CreatedAt until a decision is
	// made. The feed is ordered by it.
	ActedAt int64
	// Metered: a model on keys the operator holds decided the email, and it was counted
	// against its tenant's monthly allowance (AIEmails). It is counted once, however often
	// it is decided again.
	Metered bool

	// Where the executor last left the message; zero while it is still where it arrived.
	CurFolder      string
	CurUIDValidity uint32
	CurUID         uint32
}

Message is one row of messages: what is kept of an email. The body never is.

func (Message) Location

func (m Message) Location() mail.MsgRef

Location is where the message is on the server, as far as MailRules knows: where the executor last left it, or else where it arrived.

type Move added in v0.3.0

type Move struct {
	ID        int64
	TenantID  int64
	AccountID int64
	MessageID int64
	ActionID  int64 // the filing action
	From      string
	RuleID    int64
	RuleName  string
	Filed     string // where MailRules filed it
	Chosen    string // where it was found; "" = in no folder
	Inbox     bool   // Chosen is the mailbox's watched folder
	// Person: Chosen is the Inbox or an ordinary folder, so the person filed it there; not
	// deleted, archived or put there by MailRules itself. Only such moves reach the module.
	Person    bool
	FiledAt   int64
	CreatedAt int64 // when the watcher found it moved
	Attempts  int

	// Read back only.
	Mailbox string // the mailbox's address, as notices name it
	Count   int    // the person's moves of this sender to Chosen kept so far, this one included
}

Move is an email the person moved out of the folder a MailRules action filed it in.

type NewRule

type NewRule struct {
	Rule     rules.Rule
	Position *int // 0 = first; nil, or past the end = last
}

NewRule is a rule to create and where in the priority order to put it.

type Notice added in v0.3.0

type Notice struct {
	ID            int64
	TenantID      int64
	DestinationID int64
	AccountID     int64
	MessageID     int64 // the Activity id
	RuleID        int64 // 0 once the rule was deleted
	RuleName      string
	CreatedAt     int64 // when the rule acted
	Attempts      int   // hand-overs the module refused so far
	NoticePayload
}

Notice is one waiting notice: what a rule did to one email, for one destination.

type NoticePayload added in v0.3.0

type NoticePayload struct {
	Mailbox  string   `json:"mailbox"`
	Actions  []string `json:"actions"`
	Folder   string   `json:"folder"`
	From     string   `json:"from"`
	FromName string   `json:"from_name"`
	Subject  string   `json:"subject"`
}

NoticePayload is the part of a notice that holds the email's own details, kept only until the module takes the notice.

type Plan added in v0.3.0

type Plan struct {
	Plan      string // PlanPersonal, PlanPro, PlanTeam or PlanEnded
	Interval  string // IntervalMonth, IntervalYear or empty
	Status    string // StatusActive, StatusPastDue, StatusCancelsAtPeriodEnd or empty
	PeriodEnd int64  // the end of the paid period, unix seconds; 0 = not known
	Record    string // the module's own record of the tenant (its billing ids); never shown
	// Seats is how many seats the tenant bought of a plan sold by the seat (PlanTeam), at
	// least 1; 0 for any other plan.
	Seats int
	// CompanySignInAddOn is whether the company sign-in add-on is on the tenant's Team
	// subscription (MAI-246); false for any other plan.
	CompanySignInAddOn bool
}

Plan is a tenant's plan as a billing module reports it.

type PlanState added in v0.3.0

type PlanState struct {
	Plan
	// TrialEnd is when the tenant's trial ends (or ended): its creation plus TrialDays.
	TrialEnd int64
	// EndedAt is when the plan ended; 0 while one runs. A trial ends at TrialEnd, a plan
	// cancelled at its period's end at PeriodEnd.
	EndedAt int64
	// Warned is how many of the end-of-plan warning emails were sent; SecretsDeleted whether
	// the end-of-plan job deleted the tenant's passwords, tokens and model keys.
	Warned         int
	SecretsDeleted bool
}

PlanState is a tenant's plan as it stands at a moment (TenantPlan).

func (PlanState) Ended added in v0.3.0

func (p PlanState) Ended() bool

Ended reports whether the tenant's plan is over: nothing is sorted and the app is read-only.

type Question added in v0.3.0

type Question struct {
	ID        int64
	TenantID  int64
	AccountID int64  // the mailbox of the latest move asked about
	Mailbox   string // its address
	From      string
	Inbox     bool   // the destination is the Inbox (the mailbox's watched folder)
	Folder    string // the destination's folder; the watched folder's name for the Inbox
	Emails    int    // how many of the sender's emails the person moved there
	Status    string // QuestionOpen | QuestionYes | QuestionNo
	AskedAt   int64
}

Question is one question Activity asks about the person's moves of one sender to one destination.

type Removal added in v0.3.0

type Removal struct {
	TenantID int64
	// Tenant is whether the whole tenant went: the user was its only member.
	Tenant bool
	// Accounts are the mailboxes deleted.
	Accounts []int64
	// Rules are the team's rules rewritten because a mailbox they named was deleted (as
	// DeleteAccount rewrites them), when the tenant stays.
	Rules []rules.Rule
}

Removal is what RemoveForgotten removed.

type RemovedMailbox added in v0.3.0

type RemovedMailbox struct {
	ID        int64 // the row's own: the same on every hand-over
	TenantID  int64 // the tenant the row was read in; 0 on SQLite
	AccountID int64
	Preset    string
	OAuth     bool // connected with one-click sign-in; else through the module's sign-in form
	Username  string
	CreatedAt int64
	Attempts  int
}

RemovedMailbox is a mailbox a module connected whose secret the daemon let go of, waiting to be handed to that module. Its secret is read with RemovedMailboxSecret.

type RuleStat

type RuleStat struct {
	Hits        int   // messages it was applied to since the time asked for
	LastMatchAt int64 // 0 = never
}

RuleStat is how often a rule has been applied.

type RuleUse

type RuleUse struct {
	RuleID   int64
	RuleName string
	Emails   int
	Calls    int // decisions a model was asked for
	CostUSD  float64
}

RuleUse is what one rule did, or cost. RuleID is 0 with a name for a rule that has been deleted since, and 0 without one for "no rule".

type SeatCheck added in v0.3.0

type SeatCheck struct {
	TenantID  int64
	Version   int64 // goes up each time it is queued again
	CreatedAt int64 // when it was first queued
	Attempts  int   // the billing module's refusals so far
}

SeatCheck is a tenant whose paid seats may be more than it uses (WaitingSeatChecks).

type SeatLimit added in v0.3.0

type SeatLimit func(p PlanState) int

SeatLimit is how many people a tenant on plan p may have: members, and invitations waiting, together. NoLimit is any number. A nil SeatLimit limits nothing.

type SeatLimitError added in v0.3.0

type SeatLimitError struct {
	Limit int // the seats the plan has
	Used  int // members and invitations waiting
}

SeatLimitError is a member or an invitation refused because every seat of the tenant's plan is taken.

func (*SeatLimitError) Error added in v0.3.0

func (e *SeatLimitError) Error() string

func (*SeatLimitError) Is added in v0.3.0

func (e *SeatLimitError) Is(target error) bool

Is makes errors.Is(err, ErrSeatLimit) hold.

type SecretsCheck added in v0.3.0

type SecretsCheck struct {
	Total      int // every stored secret
	Unreadable int // the ones the store's key provider cannot open
}

SecretsCheck counts the secrets the database holds sealed: mailbox passwords and tokens, provider keys and the addresses of notification destinations.

func (SecretsCheck) Readable added in v0.3.0

func (c SecretsCheck) Readable() int

Readable is how many secrets the key opens.

type SenderDecision

type SenderDecision struct {
	Stage      string
	RuleID     int64
	Confidence float64
	Corrected  bool // the user corrected that message afterwards
}

SenderDecision is one past decision for a sender, as the learner reads it.

type SenderSeen

type SenderSeen struct {
	Address         string
	Name            string // the From display name of the newest email; may be empty
	Messages        int    // emails seen since the time asked for
	LastSeenAt      int64
	ListUnsubscribe bool // at least one of them carried a List-Unsubscribe header
	// OneClick is set when, on at least one mailbox, one of them offers an RFC 8058
	// one-click unsubscribe link.
	OneClick bool
	// UnsubscribedAt is when MailRules last unsubscribed the address, once every such mailbox
	// is unsubscribed (unsubscribes); 0 while one is not.
	UnsubscribedAt int64
}

SenderSeen is what the messages table knows about one sender address.

type Signals

type Signals struct {
	Bulk          bool   `json:"bulk"`
	Noreply       bool   `json:"noreply"`
	Contact       bool   `json:"is_contact"`
	RepliedBefore bool   `json:"replied_before"`
	DMARC         string `json:"dmarc"`
	// ListUnsubscribe is set when the email carries a List-Unsubscribe header.
	ListUnsubscribe bool `json:"list_unsubscribe"`
}

Signals is the JSON in messages.signals.

type Snapshot

type Snapshot struct {
	Folder      string   `json:"folder"`
	UIDValidity uint32   `json:"uidvalidity"`
	UID         uint32   `json:"uid"`
	Flags       []string `json:"flags"`
}

Snapshot is the JSON in actions.before and actions.after: where a message was and which flags it had.

func (Snapshot) Ref

func (s Snapshot) Ref(accountID int64) mail.MsgRef

Ref is the snapshot's location in an account.

type Store

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

Store is the repository over the database. All times are unix seconds. keys seals and opens the secrets it keeps (WithKeys); a store without one keeps none.

func New

func New(db *sql.DB) *Store

New wraps an open, migrated database, SQLite (Open) or Postgres (OpenPostgres).

func (*Store) AIEmails added in v0.3.0

func (s *Store) AIEmails(ctx context.Context, tenantID int64, month string) (int, error)

AIEmails returns how many emails a model decided for the tenant on keys the operator holds in month (see Month).

func (*Store) AcceptInvitation added in v0.3.0

func (s *Store) AcceptInvitation(ctx context.Context, tenantID int64, email string, now int64) error

AcceptInvitation records that the person invited to the tenant at email accepted, as the sign-in service reports it. When they already joined (they signed in under the team's organisation before the report came), they get the invitation's role now and it is used up; otherwise it waits InvitationDays more for their first sign-in, which joins them with its role. ErrNotFound when the tenant has no invitation for the address that has not expired.

func (*Store) Account

func (s *Store) Account(ctx context.Context, id int64) (Account, error)

Account returns one account, or ErrNotFound. It does not look at who asks: it is for internal work on a known account (the worker, the executor). A request reads VisibleAccount.

func (*Store) AccountSecret

func (s *Store) AccountSecret(ctx context.Context, id int64) (string, error)

AccountSecret decrypts an account's app password or token. It returns crypto.ErrDecrypt when the key it was sealed under does not open, and ErrNoSecret when the secret was deleted.

func (*Store) AccountTenant added in v0.2.0

func (s *Store) AccountTenant(ctx context.Context, accountID int64) (int64, error)

AccountTenant returns the tenant an account belongs to, or ErrNotFound.

func (*Store) Accounts

func (s *Store) Accounts(ctx context.Context) ([]Account, error)

Accounts lists every account of every tenant, oldest first: for the daemon's own work (starting the watchers, the master key check), never for a request.

func (*Store) Action

func (s *Store) Action(ctx context.Context, id int64) (Action, error)

Action returns one action, or ErrNotFound. It does not look at who asks: a request reads VisibleAction.

func (*Store) Activity

func (s *Store) Activity(ctx context.Context, v Viewer, f ActivityFilter) ([]ActivityRow, error)

Activity lists the messages of the mailboxes the viewer sees, each with its latest decision, by when MailRules acted on them (ActedAt), newest first; messages acted on in the same second come newest row first.

func (*Store) ActivityFor

func (s *Store) ActivityFor(ctx context.Context, v Viewer, messageID int64) (ActivityRow, error)

ActivityFor returns the activity row of one message the viewer sees, or ErrNotFound.

func (*Store) AddBatchProgress

func (s *Store) AddBatchProgress(ctx context.Context, id int64, done, skipped, tokens int, costUSD float64) error

AddBatchProgress records that a cleanup batch handled more emails, passed over some that had moved, and what their model calls cost.

func (*Store) AddCorrection

func (s *Store) AddCorrection(ctx context.Context, tenantID, userID int64, c Correction, always string) (int64, error)

AddCorrection records a correction in one transaction: it inserts the row, forgets the tenant's learned sender rule for that message's sender and marks the message acted. With always (rules.MatchAddress or rules.MatchDomain; "" = neither) it also stores a user sender rule of the tenant, made by userID, that sends the address, or its whole domain, to the right rule (or keeps it in the inbox), replacing whatever that address or domain had. The caller checks that the message and the right rule are the tenant's.

func (*Store) AddDecision

func (s *Store) AddDecision(ctx context.Context, d Decision, state string) (int64, error)

AddDecision records a decision and moves its message to state in one transaction, and returns the decision's id. The message's ActedAt becomes the decision's time. A Metered decision counts the email against its tenant's allowance for the month of CreatedAt in the same transaction, unless the message was counted before: a decision that is not recorded is not counted, and one made again after a failure is not counted twice.

func (*Store) AddFolder

func (s *Store) AddFolder(ctx context.Context, accountID int64, name string) error

AddFolder records a folder MailRules itself just created on the server, so it shows up before the next folder discovery. A folder already on record is left as it is.

func (*Store) AddLearnedSenderRule

func (s *Store) AddLearnedSenderRule(ctx context.Context, tenantID, userID int64, address string, ruleID, now int64) (bool, error)

AddLearnedSenderRule routes an address to one of the tenant's rules unless the address already has a sender rule there, which is never overwritten. userID is who it is learnt for. It reports whether a rule was created.

func (*Store) AddUnsubscribe added in v0.3.0

func (s *Store) AddUnsubscribe(ctx context.Context, accountID int64, address, host string, now int64) error

AddUnsubscribe records that MailRules unsubscribed a mailbox from address at host (the site that took it; the link is not kept).

func (*Store) AddUsage

func (s *Store) AddUsage(ctx context.Context, tenantID int64, operator bool, day, provider, model, purpose string, calls, tokensIn, tokensOut int, costUSD float64) error

AddUsage adds model calls to the tenant's cost-ledger row for that day, provider, model and purpose (decide | escalate | compose | test | cleanup), creating it if needed. operator marks calls made with a key the operator holds (MAILRULES_MODE=cloud): they are booked on rows of their own, so billing can charge for them.

func (*Store) AdvanceFolder

func (s *Store) AdvanceFolder(ctx context.Context, ref mail.MsgRef) error

AdvanceFolder raises a watched folder's last processed UID to ref's. It never lowers it, except when the folder's UIDVALIDITY changed, where the old position means nothing.

func (*Store) AnswerQuestion added in v0.3.0

func (s *Store) AnswerQuestion(ctx context.Context, v Viewer, id int64, yes bool, now int64) (Question, error)

AnswerQuestion records the viewer's answer to an open question about a mailbox they see, in one transaction. Yes stores the sender rule a correction's "Always do this for <sender>" stores (putUserSenderRule), made by the viewer: keep the sender's mail in the Inbox, or move it to the folder; it replaces whatever the tenant had for that address. No stores nothing more. Either way the question is not asked again. A question that is not open, or not one the viewer sees, is ErrNotFound.

func (*Store) AskMove added in v0.3.0

func (s *Store) AskMove(ctx context.Context, moveID, now int64) (Question, bool, error)

AskMove puts, or keeps, the question about the move's sender and destination open, counting the person's moves kept for them: one question per sender and destination, whichever mailboxes the moves were in. It returns the question and whether it is new or changed. A question already answered is ErrAnswered; a move that is not kept, or not the person's, is ErrNotFound.

func (*Store) Audit added in v0.3.0

func (s *Store) Audit(ctx context.Context, tenantID int64, action, target, detail string) error

Audit records one row of the tenant's audit log under ctx's actor, for something done outside the store (a request to a module's service); nothing when ctx has none.

func (*Store) AuditLog added in v0.3.0

func (s *Store) AuditLog(ctx context.Context, tenantID, since, until int64) ([]AuditEntry, error)

AuditLog lists the tenant's audit log from since to until (unix seconds, both included; until 0 = now), oldest first.

func (*Store) Batch

func (s *Store) Batch(ctx context.Context, id int64) (Batch, error)

Batch returns one batch, or ErrNotFound. It does not look at who asks: a request reads VisibleBatch.

func (*Store) BatchActionCounts

func (s *Store) BatchActionCounts(ctx context.Context, v Viewer, batchID int64) (map[string]int, error)

BatchActionCounts counts a batch's own actions on the mailboxes the viewer sees, by status.

func (*Store) BatchActions

func (s *Store) BatchActions(ctx context.Context, batchID int64) ([]Action, error)

BatchActions lists a batch's actions, oldest first, whoever's mailbox they are on: for internal work. A request reads VisibleBatchActions.

func (*Store) Batches

func (s *Store) Batches(ctx context.Context, v Viewer, kind string, before int64, limit int) ([]Batch, error)

Batches lists the batches the viewer sees, newest first: of one kind, or of every kind when kind is empty. before is the cursor (only batches with a smaller id; 0 = from the newest).

func (*Store) CanSee added in v0.2.0

func (s *Store) CanSee(ctx context.Context, v Viewer, accountID int64) (bool, error)

CanSee reports whether the viewer sees the account: the visibility rule for one id.

func (*Store) CheckSecrets added in v0.3.0

func (s *Store) CheckSecrets(ctx context.Context) (SecretsCheck, error)

CheckSecrets counts every tenant's stored secrets, and those the store's key provider cannot open: whether the key-encryption key is the one they are under, checked by unwrapping each tenant's key, not by opening each secret. A secret sealed before tenant keys (envelope 1) is opened, as it is the master key's own. A store without keys opens none, which is how the caller counts what a key would have to protect before it has one. A secret that fails is counted, not an error; only a database failure is.

func (*Store) ClaimSummary

func (s *Store) ClaimSummary(ctx context.Context, userID, dueAt, now int64, maxAttempts int) (id int64, attempt int, ok bool, err error)

ClaimSummary records an attempt at the summary due at dueAt and reports whether this caller is to make it, and which attempt it is. It is one statement, so of two callers at once only one gets it. It is not to be made when it was sent, when an attempt is running or was cut off (status sending), after maxAttempts, or while the last failed attempt is younger than its back-off: a minute after the first, doubling each time.

func (*Store) Corrections

func (s *Store) Corrections(ctx context.Context, v Viewer, limit int) ([]Correction, error)

Corrections lists the newest corrections made to mail in the mailboxes the viewer sees, newest first, with the example each stored: the pool few-shot retrieval ranks.

func (*Store) CountMessages

func (s *Store) CountMessages(ctx context.Context, v Viewer, state string) (int, error)

CountMessages counts the messages in a state in the mailboxes the viewer sees, e.g. StateReview for the Needs review badge.

func (*Store) CreateAccount

func (s *Store) CreateAccount(ctx context.Context, a Account, secret string) (Account, error)

CreateAccount stores an account with its secret sealed under its tenant's data key. The account goes into its owner's (a.UserID's) tenant and starts shared or not as NewAccountShared says; a.TenantID and a.Shared are ignored. The ciphertext is bound to the row id, which only exists after the insert, so the row is inserted, sealed and updated in one transaction: no reader ever sees it without a secret. Its tenant may have any number of mailboxes; CreateAccountWithin caps them.

func (*Store) CreateAccountWithin added in v0.3.0

func (s *Store) CreateAccountWithin(ctx context.Context, a Account, secret string, max int) (Account, error)

CreateAccountWithin is CreateAccount for a tenant that may have at most max mailboxes (NoLimit: any number). The count and the insert are one write transaction, which holds the database's write lock (on Postgres, the tenant's) from its start, so two mailboxes added at once cannot both take the last place. A tenant at or over max gets a *MailboxLimitError and nothing is stored.

func (*Store) CreateBatch

func (s *Store) CreateBatch(ctx context.Context, tenantID int64, kind, status string, now int64) (int64, error)

CreateBatch opens a batch of the tenant, with no mailbox, and returns its id.

func (*Store) CreateCleanupBatch

func (s *Store) CreateCleanupBatch(ctx context.Context, accountID int64, folder string, since int64, scanLimit, scanMatched, total int, now int64) (Batch, error)

CreateCleanupBatch opens the batch of one cleanup run over total emails, running.

scanLimit and scanMatched say how much mail the check behind it covered: the most emails it was allowed and how many the range held before that cut. scanLimit 0 records nothing.

func (*Store) CreateCleanupRun added in v0.1.2

func (s *Store) CreateCleanupRun(ctx context.Context, cs []CleanupBatch, now int64) ([]Batch, error)

CreateCleanupRun opens one running cleanup batch per mailbox for a manual run over several mailboxes, all or none: they share a RunID, the id of the first, so they read back as one run. A run of one mailbox is a plain cleanup batch with no RunID.

func (*Store) CreateDestination added in v0.3.0

func (s *Store) CreateDestination(ctx context.Context, d Destination, sec DestinationSecret, now int64) (Destination, error)

CreateDestination stores a destination of the tenant with its address and secret sealed under the tenant's key, and returns it with its id.

func (*Store) CreateFirstUser

func (s *Store) CreateFirstUser(ctx context.Context, email, passwordHash string, now int64) (User, error)

CreateFirstUser inserts the admin account, in the self-host tenant, only if no user exists yet. The admin is the tenant's owner.

func (*Store) CreateInvitation added in v0.3.0

func (s *Store) CreateInvitation(ctx context.Context, tenantID int64, inv Invitation, seats SeatLimit, now int64) (Invitation, error)

CreateInvitation stores an invitation of the tenant for inv.Email with inv.Role, sent by inv.InvitedBy, expiring InvitationDays from now, and returns it. One that expired for the same address is replaced. It fails, storing nothing, with ErrBadRole, ErrAlreadyMember when a member has the address, ErrHasAccount when a user of any other tenant has it (the person must delete that account first), ErrAlreadyInvited when an invitation for it waits, and a *SeatLimitError when the tenant's plan has no seat left (the count and the insert are one write transaction, so two invitations sent at once cannot both take the last seat).

func (*Store) CreateRule

func (s *Store) CreateRule(ctx context.Context, tenantID int64, r rules.Rule, now int64) (rules.Rule, error)

CreateRule inserts a rule of the tenant at version 1 and returns it with its id. r.UserID is its author. The caller validates the rule first.

func (*Store) CreateRules

func (s *Store) CreateRules(ctx context.Context, tenantID, userID int64, added []NewRule, now int64) ([]rules.Rule, error)

CreateRules inserts rules of the tenant, written by userID, in one transaction and returns them as saved, in the order given. Each lands at its position in the priority order, or at the end; the priorities of all the tenant's rules are then renumbered from 1, which is not an edit, so versions stay. The caller validates the rules first.

func (*Store) CreateSession

func (s *Store) CreateSession(ctx context.Context, tokenHash string, userID, now, expiresAt int64) error

CreateSession stores a login session by the hash of its token, and sweeps expired ones (on Postgres, those of the user's tenant).

func (*Store) CreateTenant added in v0.2.0

func (s *Store) CreateTenant(ctx context.Context, provider, externalID string, now int64) (int64, error)

CreateTenant adds a tenant for a sign-in service's organisation and returns its id.

func (*Store) CreateUser added in v0.2.0

func (s *Store) CreateUser(ctx context.Context, tenantID int64, email, passwordHash string, now int64) (User, error)

CreateUser adds a user to a tenant: its owner when it has nobody yet, else a member. An empty passwordHash makes a user who cannot sign in with a password (one made from a sign-in service's identity).

func (*Store) DecisionDraft added in v0.3.0

func (s *Store) DecisionDraft(ctx context.Context, tenantID, decisionID int64) (a Action, ok bool, err error)

DecisionDraft returns the latest draft action recorded for a decision; ok is false when there is none.

func (*Store) DeleteAccount

func (s *Store) DeleteAccount(ctx context.Context, id, now int64) ([]rules.Rule, error)

DeleteAccount wipes an account with its secret, folders, contacts, messages, decisions, actions and corrections: each references the account with ON DELETE CASCADE. The rules are kept: first, in the same transaction, every user's rules that name the mailbox are rewritten as rules.Rule.DropAccount has it, so a rule limited to it is switched off and marked, with no mailbox, before the cascade could reach it. It returns those rules. A mailbox a module connected is queued for that module (queueRemovedMailboxes).

func (*Store) DeleteDestination added in v0.3.0

func (s *Store) DeleteDestination(ctx context.Context, tenantID, id, now int64) ([]rules.Rule, error)

DeleteDestination removes one of the tenant's destinations with its delivery log and the notices still waiting for it, and takes it off every rule of the tenant that named it, in one transaction. It returns the rules it changed, as saved (an edit: their versions go up). A destination of another tenant is ErrNotFound.

func (*Store) DeleteOwnAccount added in v0.3.0

func (s *Store) DeleteOwnAccount(ctx context.Context, provider, subject string, now int64) (Forget, error)

DeleteOwnAccount is a person who deleted their own account: ForgetIdentity, and in the same transaction their user lets go of the identity, so that it never gets their data back: the same identity signing in again, before the removal or after, gets a new, empty user. When they are their tenant's only member, the tenant lets go of the sign-in service's organisation too, so that user gets a new tenant instead of joining the old data's. The old data is removed after ForgetDays all the same, and the service's own deletion notice for the identity afterwards reaches nobody.

func (*Store) DeleteRule

func (s *Store) DeleteRule(ctx context.Context, tenantID, id int64) error

DeleteRule removes one of the tenant's rules. The schema takes the sender rules that route to it along (ON DELETE CASCADE) and keeps its decisions and corrections, with their rule set to NULL.

func (*Store) DeleteSenderRule

func (s *Store) DeleteSenderRule(ctx context.Context, tenantID int64, matchType, value string) error

DeleteSenderRule removes one of the tenant's sender verdicts; deleting a missing one is not an error.

func (*Store) DeleteSession

func (s *Store) DeleteSession(ctx context.Context, tokenHash string) error

DeleteSession ends a session; deleting a missing one is not an error.

func (*Store) DeleteTenantSecrets added in v0.3.0

func (s *Store) DeleteTenantSecrets(ctx context.Context, tenantID, endedAt, now int64) ([]int64, error)

DeleteTenantSecrets deletes, in one transaction, the secrets of a tenant whose plan ended at endedAt, once EndedSecretsDays have passed since by now: every mailbox's password or token (as ForgetIdentity deletes an only member's: each one a module connected is queued for it first, and they show auth_failed, or reconnect_needed, saying the plan ended), the tenant's stored model API keys, and its notification destinations' addresses and signing secrets; the plan records it (PlanState.SecretsDeleted). It returns the tenant's mailboxes, whose supervisors the caller stops before and after. Everything else stays, readable. ErrPlanNotEnded, changing nothing, when the plan no longer ended at endedAt or not long enough ago. Never the self-host tenant.

func (*Store) Destination added in v0.3.0

func (s *Store) Destination(ctx context.Context, id int64) (Destination, error)

Destination returns one destination, or ErrNotFound. It does not look at who asks: it is for the delivery worker.

func (*Store) DestinationSecret added in v0.3.0

func (s *Store) DestinationSecret(ctx context.Context, id int64) (DestinationSecret, error)

DestinationSecret opens a destination's address and secret. ErrNoSecret when they were deleted (Destination.AddressDeleted), ErrNotFound when there is no such destination.

func (*Store) Destinations added in v0.3.0

func (s *Store) Destinations(ctx context.Context, tenantID int64) ([]Destination, error)

Destinations lists the tenant's destinations, oldest first.

func (*Store) DoneActionsSince

func (s *Store) DoneActionsSince(ctx context.Context, v Viewer, ruleID, since int64) ([]Action, error)

DoneActionsSince lists the actions on the mailboxes the viewer sees made at or after since that are still in effect, newest first: the ones "undo everything since" reverses. With ruleID, only those a decision for that rule led to.

func (*Store) DraftDone added in v0.3.0

func (s *Store) DraftDone(ctx context.Context, j DraftJob) error

DraftDone deletes a draft job that is settled: its draft action records how.

func (*Store) DraftRetry added in v0.3.0

func (s *Store) DraftRetry(ctx context.Context, j DraftJob, next int64) error

DraftRetry records that a draft job could not be done now; it is tried again at next.

func (*Store) DryRun

func (s *Store) DryRun(ctx context.Context, tenantID int64, def bool) (bool, error)

DryRun reports whether the tenant's dry-run switch is on. def is used until the switch has been set (MAILRULES_DRY_RUN).

func (*Store) DueForRemoval added in v0.3.0

func (s *Store) DueForRemoval(ctx context.Context, now int64) ([]Forgotten, error)

DueForRemoval lists the forgotten users whose removal time has come by now, the earliest first.

func (*Store) DueMessages

func (s *Store) DueMessages(ctx context.Context, accountID, now int64) ([]Message, error)

DueMessages lists an account's failed messages whose retry time has come, oldest first.

func (*Store) EndIdentitySessions added in v0.2.0

func (s *Store) EndIdentitySessions(ctx context.Context, provider, subject string) error

EndIdentitySessions deletes every session of the identity's user. An identity nobody has is not an error.

func (*Store) EndTrial added in v0.3.0

func (s *Store) EndTrial(ctx context.Context, tenantID, now int64) (bool, error)

EndTrial records, at now, the end of a trial over with no plan bought: a plans row with plan ended at the trial's end and no warning sent yet, so the end-of-plan job has where to keep its steps. It reports whether it wrote one; a tenant with a row already, whose trial runs, or the self-host tenant (which has no plan) is left as it is.

func (*Store) EndedPlan added in v0.3.0

func (s *Store) EndedPlan(ctx context.Context, tenantID, now int64) (EndedPlan, error)

EndedPlan returns the tenant's plan at now (TenantPlan) with when its latest warning was sent. ErrNotFound for a tenant that does not exist.

func (*Store) ExpireRemovedMailboxes added in v0.3.0

func (s *Store) ExpireRemovedMailboxes(ctx context.Context, now int64) ([]RemovedMailbox, error)

ExpireRemovedMailboxes deletes, with their secrets, the removed mailboxes that waited RemovedMailboxDays by now without being taken, and returns them.

func (*Store) ExtendSession

func (s *Store) ExtendSession(ctx context.Context, tokenHash string, expiresAt int64) error

ExtendSession slides a session's expiry forward.

func (*Store) FailRunningBatches

func (s *Store) FailRunningBatches(ctx context.Context) error

FailRunningBatches marks every batch still running as failed. The daemon calls it at startup: a batch that was running when the daemon stopped will never finish.

func (*Store) FiledSince added in v0.3.0

func (s *Store) FiledSince(ctx context.Context, accountID, since, afterID int64, limit int) ([]Filed, error)

FiledSince lists, oldest first, the account's emails a MailRules action filed at or after since, with an action id above afterID, that no later action moved on and that have no move recorded yet: at most limit. Dry-run, failed and undone actions file nothing. An email without a Message-ID or a sender cannot be followed and is left out. The rule is the decision's, or, for a fix made in MailRules, the rule it was fixed to.

func (*Store) FinishAction

func (s *Store) FinishAction(ctx context.Context, a Action, then FollowUps) error

FinishAction stores how an action ended. For a done action, after is the message's state afterwards, and the message's location is updated with it in the same transaction. then says what a, the last action of its rule's run on the message (done, or the failure that stopped the run), starts: notices (queueNotices) and a draft reply (queueDraft).

func (*Store) FinishSummary

func (s *Store) FinishSummary(ctx context.Context, id int64, sent bool, periodStart, periodEnd, now int64) error

FinishSummary records how a claimed attempt ended: sent, with the time it covered, or failed.

func (*Store) FirstCleanupUsed added in v0.3.0

func (s *Store) FirstCleanupUsed(ctx context.Context, tenantID int64) (int, error)

FirstCleanupUsed returns how much of its one-time first-cleanup grant the tenant's cleanup checks have used: emails a model decided on keys the operator holds. It never resets.

func (*Store) FirstUser

func (s *Store) FirstUser(ctx context.Context) (User, error)

FirstUser returns the admin account, or ErrNotFound before first-run setup. The command line acts as this user, in its tenant.

func (*Store) Folder

func (s *Store) Folder(ctx context.Context, accountID int64, name string) (Folder, error)

Folder returns one folder, or ErrNotFound.

func (*Store) Folders

func (s *Store) Folders(ctx context.Context, accountID int64) ([]Folder, error)

Folders lists an account's folders by name.

func (*Store) ForgetIdentity added in v0.3.0

func (s *Store) ForgetIdentity(ctx context.Context, provider, subject string, now int64) (Forget, error)

ForgetIdentity is a person the sign-in service deleted. In one transaction it ends every session of the identity's user, frees their email (a new identity may sign up with it at once), deletes their secrets and schedules the removal of the rest of their data for ForgetDays from now. The secrets are the passwords and tokens of their mailboxes (every mailbox of the tenant when they are its only member, else the ones they own, shared or not), which then show auth_failed asking for a new password, and, when they are the tenant's only member, the tenant's stored model API keys. A mailbox a module connected is first queued for that module with its secret (queueRemovedMailboxes). The caller stops those mailboxes' supervisors before (IdentityMailboxes) and after (Forget.Accounts) it. Everything else is left as it is until the removal. An identity nobody has is not an error, and forgetting one again changes nothing, the removal time included. Only users made from an identity are reached, so the self-host admin never is.

func (*Store) HasUsers

func (s *Store) HasUsers(ctx context.Context) (bool, error)

HasUsers reports whether first-run setup has been done: whether any tenant has a user.

func (*Store) HitSenderRule

func (s *Store) HitSenderRule(ctx context.Context, id int64) error

HitSenderRule counts one more email settled by a sender rule.

func (*Store) IdentityMailboxes added in v0.3.0

func (s *Store) IdentityMailboxes(ctx context.Context, provider, subject string) ([]int64, error)

IdentityMailboxes lists the mailboxes ForgetIdentity would take the secrets of, as things stand. Their supervisors are stopped before ForgetIdentity runs. An identity nobody has has none.

func (*Store) ImportRules

func (s *Store) ImportRules(ctx context.Context, tenantID, userID int64, imported []rules.Rule, now int64) (created, updated int, err error)

ImportRules stores the rules of a YAML file in the tenant in one transaction; userID is the author of the rules it adds. A rule whose name is already in use replaces that rule, including its mailbox (AccountID, which the caller has set to the one to keep when the file says nothing), and keeps its place, id and author; the others are added after the existing rules, in file order. Rules the file does not name are left alone. The caller validates the rules first.

func (*Store) IngestMessage

func (s *Store) IngestMessage(ctx context.Context, ref mail.MsgRef, now int64) (m Message, fresh bool, err error)

IngestMessage records that a message arrived, once. fresh is false when the row was already there (the watcher repeats a message after a crash); the stored row is returned either way. A known message that the executor itself put at ref (an undo back into the watched folder gets a new UID) is not new mail either: its existing row is returned.

func (*Store) InsertAction

func (s *Store) InsertAction(ctx context.Context, a Action) (int64, error)

InsertAction writes an action row and returns its id.

func (*Store) Invitations added in v0.3.0

func (s *Store) Invitations(ctx context.Context, tenantID, now int64) ([]Invitation, error)

Invitations lists the tenant's invitations that have not expired at now, oldest first.

func (*Store) IsContact

func (s *Store) IsContact(ctx context.Context, accountID int64, address string) (bool, error)

IsContact reports whether the account's owner has sent mail to address.

func (*Store) LastMailAt

func (s *Store) LastMailAt(ctx context.Context, accountID int64) (int64, error)

LastMailAt is when the newest email MailRules has seen in an account arrived (its received date, or when it was first seen if it carried none); 0 when it has seen none.

func (*Store) LastOwner added in v0.3.0

func (s *Store) LastOwner(ctx context.Context, u User) (bool, error)

LastOwner reports whether the user is the only owner of a tenant that has other members: a team that would be left with nobody to manage its plan and members.

func (*Store) LastSentSummary

func (s *Store) LastSentSummary(ctx context.Context, userID int64) (Summary, error)

LastSentSummary returns the user's latest summary that went out, or ErrNotFound.

func (*Store) LeaveOwnMail

func (s *Store) LeaveOwnMail(ctx context.Context, tenantID int64) (bool, error)

LeaveOwnMail reports whether, in the tenant, mail from the mailbox's own address is left alone. The pipeline reads it for every email.

func (*Store) LeftByMailRules added in v0.3.0

func (s *Store) LeftByMailRules(ctx context.Context, accountID int64, folder string, uidValidity, uid uint32, exceptMessage int64) (bool, error)

LeftByMailRules reports whether MailRules itself left an email at (folder, uidValidity, uid) of the account: it is where the executor last put a row other than exceptMessage (a cleanup that sorted the same email under a row of its own).

func (*Store) LiveBatch

func (s *Store) LiveBatch(ctx context.Context, tenantID int64, now time.Time) (int64, error)

LiveBatch returns the batch that collects the tenant's actions of live processing for now's day (UTC), creating it on the day's first action, so "undo today" is one batch undo. Once a day's batch has been undone, later mail that day starts a new one.

func (*Store) MarkActionUndone

func (s *Store) MarkActionUndone(ctx context.Context, a Action, restored mail.MsgRef, now int64) error

MarkActionUndone marks a done action undone and records where its message is now.

func (*Store) MasterKeyUses added in v0.3.0

func (s *Store) MasterKeyUses(ctx context.Context) (MasterKeyUses, error)

MasterKeyUses counts, over every tenant, what still needs the master key. It needs no key itself.

func (*Store) Members added in v0.3.0

func (s *Store) Members(ctx context.Context, tenantID int64) ([]Member, error)

Members lists the tenant's members, the longest-standing first.

func (*Store) Message

func (s *Store) Message(ctx context.Context, id int64) (Message, error)

Message returns one message, or ErrNotFound.

func (*Store) MessageActions

func (s *Store) MessageActions(ctx context.Context, messageID int64) ([]Action, error)

MessageActions lists every action taken on a message, oldest first.

func (*Store) MessageActivity added in v0.2.0

func (s *Store) MessageActivity(ctx context.Context, messageID int64) (ActivityRow, error)

MessageActivity returns one message's activity row, whoever's it is, or ErrNotFound: for internal work on a known message (the executor, the event publishers).

func (*Store) MessageByMessageID

func (s *Store) MessageByMessageID(ctx context.Context, accountID int64, messageID string, except int64) (Message, error)

MessageByMessageID returns the account's newest row, other than except, for the email with this Message-ID header, or ErrNotFound.

func (*Store) MessageCorrections

func (s *Store) MessageCorrections(ctx context.Context, messageID int64) ([]Correction, error)

MessageCorrections lists the corrections the user made to a message, oldest first.

func (*Store) MessageDecisions

func (s *Store) MessageDecisions(ctx context.Context, messageID int64) ([]Decision, error)

MessageDecisions lists every decision made for a message, oldest first. A message has more than one when the retry job ran it again.

func (*Store) MoveHanded added in v0.3.0

func (s *Store) MoveHanded(ctx context.Context, id, now int64) error

MoveHanded records that the module took a move.

func (*Store) MoveRetry added in v0.3.0

func (s *Store) MoveRetry(ctx context.Context, id, next int64) error

MoveRetry records that the module refused a move, to be handed over again at next.

func (*Store) NoticeHanded added in v0.3.0

func (s *Store) NoticeHanded(ctx context.Context, id, now int64) error

NoticeHanded records that the module took a notice: it is not handed over again, and the email's details it held are blanked.

func (*Store) NoticeRetry added in v0.3.0

func (s *Store) NoticeRetry(ctx context.Context, id, next int64) error

NoticeRetry records that the module refused a notice; it is handed over again at next.

func (*Store) OpenQuestions added in v0.3.0

func (s *Store) OpenQuestions(ctx context.Context, v Viewer) ([]Question, error)

OpenQuestions lists the open questions about mailboxes the viewer sees, newest first.

func (*Store) Ping

func (s *Store) Ping(ctx context.Context) error

Ping reports whether the database answers.

func (*Store) PlanEndTenants added in v0.3.0

func (s *Store) PlanEndTenants(ctx context.Context, now int64) ([]int64, error)

PlanEndTenants lists, by id, the tenants other than SelfHostTenant whose plan may have ended by now: those that never bought one and whose trial is over, those the billing module or the job recorded as ended, and those cancelled at a period's end that has passed. It is where the end-of-plan job looks; whether each one ended is TenantPlan's to say (EndedPlan).

func (*Store) PlanWarned added in v0.3.0

func (s *Store) PlanWarned(ctx context.Context, tenantID, endedAt int64, n int, now int64) error

PlanWarned records that warning n (1 to 3) about the plan that ended at endedAt was sent at now. A lower or equal number than the one recorded changes nothing. ErrPlanNotEnded, changing nothing, when the plan no longer ended at endedAt. Never the self-host tenant.

func (*Store) PutSenderRule

func (s *Store) PutSenderRule(ctx context.Context, tenantID int64, sr rules.SenderRule, now int64) (rules.SenderRule, error)

PutSenderRule stores the tenant's verdict for a sender address or domain, replacing any earlier one for the same sender, and returns it with its id. sr.UserID is who made it. A rule_id that is not one of the tenant's rules is refused as ErrNotFound.

func (*Store) QueueSeatCheck added in v0.3.0

func (s *Store) QueueSeatCheck(ctx context.Context, tenantID, now int64) error

QueueSeatCheck queues a check of the tenant's seats, due at now: for a seat bought for an invitation that was then not kept.

func (*Store) QuietRules

func (s *Store) QuietRules(ctx context.Context, v Viewer, since int64) (int, error)

QuietRules counts the enabled rules of the viewer's tenant that were applied to no email of the mailboxes the viewer sees since `since`.

func (*Store) Rebind

func (s *Store) Rebind(ctx context.Context, fresh, known int64, ref mail.MsgRef) (bool, error)

Rebind records that the email of row known is now at ref, where it was just ingested as row fresh, and removes fresh, so the email keeps one row. fresh must be a row MailRules has done nothing with yet: when it has a decision, an action or a correction, Rebind changes nothing and reports false.

func (*Store) RecentDeliveries added in v0.3.0

func (s *Store) RecentDeliveries(ctx context.Context, tenantID int64, perDestination int) (map[int64][]Delivery, error)

RecentDeliveries lists, for each of the tenant's destinations, the last perDestination deliveries reported, newest first.

func (*Store) RecentSenderDecisions

func (s *Store) RecentSenderDecisions(ctx context.Context, v Viewer, address string, n int) ([]SenderDecision, error)

RecentSenderDecisions returns the decisions of the newest n decided emails from one address across the mailboxes the viewer sees, newest first. An email decided more than once (a retry, a cleanup run) counts once, by its latest decision. A decision whose actions were recorded in dry-run is left out, so a dry-run period teaches the learner nothing, then or once MailRules is live.

func (*Store) ReconcileRemovedMailboxes added in v0.1.3

func (s *Store) ReconcileRemovedMailboxes(ctx context.Context, now int64) ([]rules.Rule, error)

ReconcileRemovedMailboxes fixes the rules that still name a mailbox removed before DeleteAccount kept them (MAI-132): every mailbox a rule names that has no account any more is dropped from it as DeleteAccount would have, for every tenant, each in one transaction of its own. It returns the rules it changed, by id; a second run changes nothing.

func (*Store) RecordDelivery added in v0.3.0

func (s *Store) RecordDelivery(ctx context.Context, noticeID int64, sent bool, reason string, at int64) (tenantID int64, err error)

RecordDelivery stores what a module reported of delivering a notice: sent, or failed and why. It returns the notice's tenant. A notice that is no longer kept (its destination or mailbox was removed, or it is older than NoticeDays) is ErrNotFound.

func (*Store) RecordMove added in v0.3.0

func (s *Store) RecordMove(ctx context.Context, m Move) (int64, error)

RecordMove writes a move the watcher found, once per filing action, provided the action is still in effect (nothing undid it meanwhile). A move that is not the person's is written as already handed over: the module never gets it. It returns the move's id, or 0 when nothing was written.

func (*Store) RemovalAccounts added in v0.3.0

func (s *Store) RemovalAccounts(ctx context.Context, userID int64) ([]int64, error)

RemovalAccounts lists the mailboxes removing the user would delete as things stand: every mailbox of their tenant when nobody else is left in it, else the ones they own, shared or not. Their supervisors are stopped before RemoveForgotten runs.

func (*Store) RemoveEndedTenant added in v0.3.0

func (s *Store) RemoveEndedTenant(ctx context.Context, tenantID, endedAt, now int64) (Removal, error)

RemoveEndedTenant removes, in one transaction, a tenant whose plan ended at endedAt, once EndedRemovalDays have passed since by now: everything RemoveForgotten removes with a tenant's only member, for every member, the plan too. A mailbox a module connected that still has its secret is queued for that module first. It returns what went; the caller stops the mailboxes before. ErrPlanNotEnded, changing nothing, when the plan no longer ended at endedAt or not long enough ago. Never the self-host tenant.

func (*Store) RemoveForgotten added in v0.3.0

func (s *Store) RemoveForgotten(ctx context.Context, userID, now int64) (Removal, error)

RemoveForgotten removes, in one transaction, what MailRules kept of a forgotten user whose removal time has come by now. When they are their tenant's only member the whole tenant goes: its mailboxes with their credentials, folders, contacts, messages, decisions, actions and corrections, its rules, sender rules, batches, settings (provider keys included), usage ledger, and notification destinations with their notices and delivery log, the user with their sessions, identity and summaries, and the tenant itself. A member of a team takes all their mailboxes, shared or not (the team's rules naming one are rewritten as DeleteAccount does), with the notices about their mail, their sessions, identity, summaries and user row; the authorship of their rules and sender rules passes to the team (handOver). It answers ErrNotFound, changing nothing, for a user who is not forgotten or not yet due: one who signed in again, or was removed already.

func (*Store) RemoveMember added in v0.3.0

func (s *Store) RemoveMember(ctx context.Context, provider, subject string, now int64) (Forget, error)

RemoveMember is a member an owner or admin removed from their team: ForgetIdentity, recorded in the audit log as a removal, and in the same transaction their user lets go of the identity, as DeleteOwnAccount's does (MAI-253): the sign-in service has taken them out of the team's organisation, so they cannot come back to their data, and the same identity signing in under another organisation gets a new tenant of their own instead of ErrTenantMismatch. The tenant remembers the identity was removed (removed_identities), so signing in under its organisation again joins it only with a new invitation (SignInIdentity). Their old user's data goes on the team's schedule, ForgetDays later.

func (*Store) RemovedMailboxRetry added in v0.3.0

func (s *Store) RemovedMailboxRetry(ctx context.Context, id, next int64) error

RemovedMailboxRetry records that a removed mailbox was not taken; it is handed over again at next.

func (*Store) RemovedMailboxSecret added in v0.3.0

func (s *Store) RemovedMailboxSecret(ctx context.Context, id int64) (string, error)

RemovedMailboxSecret opens the secret a removed mailbox had. It returns crypto.ErrDecrypt when the key-encryption key does not open it, and ErrNotFound when the row is gone.

func (*Store) RemovedMailboxTaken added in v0.3.0

func (s *Store) RemovedMailboxTaken(ctx context.Context, id int64) error

RemovedMailboxTaken deletes a removed mailbox the module took, with its secret.

func (*Store) ReorderRules

func (s *Store) ReorderRules(ctx context.Context, tenantID int64, ids []int64) error

ReorderRules sets the priorities of the tenant's rules to the order of ids, in one transaction. A reorder is not an edit, so versions stay as they are.

func (*Store) ReplaceUnopenableTenantKeys added in v0.3.0

func (s *Store) ReplaceUnopenableTenantKeys(ctx context.Context) (int, error)

ReplaceUnopenableTenantKeys drops every tenant key the store's key provider cannot unwrap, so the next secret sealed for that tenant gets a new key: what MAILRULES_NEW_MASTER_KEY asks for once the old master key is gone for good. The secrets sealed under a dropped key can never be opened again, so they go with it, in the same transaction, as ForgetIdentity deletes secrets: a mailbox shows its password deleted (Account.SecretGone), a destination its address deleted, a model key is not set. The person enters them again. It returns how many tenants' keys it dropped; with the right key it drops none. Only a wrong key (crypto.ErrDecrypt) drops one: a provider that cannot answer stops it with crypto.ErrKeyUnavailable, having dropped nothing more.

func (*Store) Retain

func (s *Store) Retain(ctx context.Context, tenantID, now int64, retentionDays int) (blanked, deleted int64, err error)

Retain is the retention job's one step for one tenant: it blanks the snippet of every message of the tenant's mailboxes first seen more than retentionDays ago (the tenant's retention_days), and deletes the tenant's messages first seen more than KeepMessagesDays ago that have no action left to undo, that is, none in effect from the last UndoDays (their decisions, actions and corrections go with them). It returns how many rows each part touched. It also deletes the tenant's notices and delivery records older than NoticeDays, the moves the move watcher found more than MoveDays ago, the audit log's rows older than AuditDays and the invitations that expired, which it does not count.

func (*Store) RevokeInvitation added in v0.3.0

func (s *Store) RevokeInvitation(ctx context.Context, tenantID, id int64, why string, now int64) (Invitation, error)

RevokeInvitation deletes one of the tenant's invitations and returns it, recorded as revoked, or as withdrawn with why when the daemon takes it back itself (it could not be sent). The seat it held is checked for (queueSeatCheck). ErrNotFound for one it does not have.

func (*Store) RewrapTenantKeys added in v0.3.0

func (s *Store) RewrapTenantKeys(ctx context.Context) (int, error)

RewrapTenantKeys wraps again, under the store's key provider, every tenant key wrapped under another KEK (tenants.kek_id): what moving an install from the master key to a module's key service (ext.Module.Keys) does, once per tenant. The secrets stay as they are, since they are sealed under the tenant's key, which does not change. Each tenant is one compare-and-set, so a second run, or two daemons at once, rewrap nothing twice. A tenant key that does not unwrap (crypto.ErrDecrypt: the master key is not the one it was wrapped under, or is gone) is left as it is; MasterKeyUses still counts it. A provider that cannot answer stops it with crypto.ErrKeyUnavailable: what is left is rewrapped at the next run. It returns how many it rewrapped.

func (*Store) Rule

func (s *Store) Rule(ctx context.Context, tenantID, id int64) (rules.Rule, error)

Rule returns one of the tenant's rules, or ErrNotFound.

func (*Store) RuleCosts

func (s *Store) RuleCosts(ctx context.Context, v Viewer, since int64) ([]RuleUse, error)

RuleCosts lists, per rule, the emails of the mailboxes the viewer sees decided for it since `since` and what the model decisions among them cost, dearest first. Every decision counts here, a retry too: it was paid for.

func (*Store) RuleStats

func (s *Store) RuleStats(ctx context.Context, v Viewer, since int64) (map[int64]RuleStat, error)

RuleStats counts, per rule of the viewer's tenant, the messages it was applied to since `since` in the mailboxes the viewer sees, with the time of the latest one ever. Mail waiting in Needs review does not count: nothing was applied.

func (*Store) Rules

func (s *Store) Rules(ctx context.Context, tenantID int64) ([]rules.Rule, error)

Rules returns all of the tenant's rules, disabled ones included, in priority order.

func (*Store) RunInEffect added in v0.3.0

func (s *Store) RunInEffect(ctx context.Context, tenantID, decisionID int64) (bool, error)

RunInEffect reports whether what a decision's rule did to its email still stands: at least one of its actions (a draft aside) is done and none was undone since.

func (*Store) SaveFolders

func (s *Store) SaveFolders(ctx context.Context, accountID int64, folders []Folder) error

SaveFolders replaces an account's folder list with what discovery found. Watch positions of folders that still exist are kept; folders that are gone are removed.

func (*Store) SaveMessageSummary

func (s *Store) SaveMessageSummary(ctx context.Context, m Message) error

SaveMessageSummary stores the parsed header fields, snippet and signals of a message.

func (*Store) SeatCheckDone added in v0.3.0

func (s *Store) SeatCheckDone(ctx context.Context, c SeatCheck) error

SeatCheckDone deletes a seat check the worker handled, unless it was queued again since it was listed (c.Version): that one waits to be handled in turn.

func (*Store) SeatCheckRetry added in v0.3.0

func (s *Store) SeatCheckRetry(ctx context.Context, c SeatCheck, next int64) error

SeatCheckRetry records that the billing module did not agree to a seat check: it is handled again at next, unless it was queued again since it was listed (it is due then).

func (*Store) SeatsUsed added in v0.3.0

func (s *Store) SeatsUsed(ctx context.Context, tenantID, now int64) (int, error)

SeatsUsed counts the seats the tenant has taken at now: its members (a removed or forgotten person, whose removal waits, is not one) and the invitations waiting. It is the one count of seats: the Members screen, Plan and billing, the downgrade check and the checks that refuse a person over the seats (takeSeat) all use it.

func (*Store) SenderRules

func (s *Store) SenderRules(ctx context.Context, tenantID int64) ([]rules.SenderRule, error)

SenderRules returns all of the tenant's sender rules.

func (*Store) SendersSeen

func (s *Store) SendersSeen(ctx context.Context, v Viewer, since int64) ([]SenderSeen, error)

SendersSeen lists every address the mailboxes the viewer sees have had mail from since `since` (by when MailRules first saw the email).

func (*Store) SessionUser

func (s *Store) SessionUser(ctx context.Context, tokenHash string, now int64) (User, int64, error)

SessionUser returns the user behind a live session and when it expires.

func (*Store) SetAccountCapabilities

func (s *Store) SetAccountCapabilities(ctx context.Context, id int64, capabilities []string) error

SetAccountCapabilities stores what the server advertised at the last login.

func (*Store) SetAccountCert added in v0.3.0

func (s *Store) SetAccountCert(ctx context.Context, id int64, fingerprint string) error

SetAccountCert stores the server certificate the person accepted for an account; "" leaves the decision to the system's trust store again.

func (*Store) SetAccountSecret

func (s *Store) SetAccountSecret(ctx context.Context, id int64, secret string) error

SetAccountSecret replaces an account's app password, sealed under its tenant's key. The change is recorded under ctx's actor: one rotated by a provider is made WithoutActor.

func (*Store) SetAccountShared added in v0.2.0

func (s *Store) SetAccountShared(ctx context.Context, id int64, shared bool) error

SetAccountShared shares an account with everyone in its tenant, or makes it private to its owner again. The caller checks that the owner asks.

func (*Store) SetAccountStatus

func (s *Store) SetAccountStatus(ctx context.Context, id int64, status, lastError string, now int64) error

SetAccountStatus records the connection state; lastError is cleared by passing "".

func (*Store) SetBatchProgress

func (s *Store) SetBatchProgress(ctx context.Context, id int64, total, done int) error

SetBatchProgress records how far a batch has come.

func (*Store) SetBatchStatus

func (s *Store) SetBatchStatus(ctx context.Context, id int64, status string) error

SetBatchStatus moves a batch to a status.

func (*Store) SetDryRun

func (s *Store) SetDryRun(ctx context.Context, tenantID int64, on bool) error

SetDryRun turns the tenant's dry-run switch on or off. A running daemon reads it before every action, so it takes effect at once.

func (*Store) SetFolderPosition

func (s *Store) SetFolderPosition(ctx context.Context, accountID int64, name string, uidValidity, lastUID uint32) error

SetFolderPosition records how far a watched folder has been processed.

func (*Store) SetInvitationRecord added in v0.3.0

func (s *Store) SetInvitationRecord(ctx context.Context, tenantID, id int64, rec string) error

SetInvitationRecord keeps the module's reference to one of the tenant's invitations. ErrNotFound for one it does not have.

func (*Store) SetMessageState

func (s *Store) SetMessageState(ctx context.Context, id int64, state string, attempts int, nextAttemptAt int64) error

SetMessageState moves a message to a state. nextAttemptAt is when the retry job should run it again; 0 means it is not waiting.

func (*Store) SetPassword added in v0.1.2

func (s *Store) SetPassword(ctx context.Context, userID int64, passwordHash string) error

SetPassword replaces a user's password hash and, in the same transaction, ends every session the user has: a session someone else holds does not outlive the password that opened it. A missing user is ErrNotFound.

func (*Store) SetPlan added in v0.3.0

func (s *Store) SetPlan(ctx context.Context, tenantID int64, p Plan, now int64) (before, after PlanState, err error)

SetPlan stores the tenant's plan as the billing module reports it, at now, and returns the state before and after. A plan that runs (personal, pro or team) clears what the end of an earlier one left: when it ended, the warnings sent, the secrets deleted. One that ends, or a report that leaves it ended (a cancelled plan whose period is over, reported again), keeps the time it really ended (the trial's end, or the paid period's) when that is earlier than now, and the end-of-plan job's progress. A change of plan, interval, status, seats or the company sign-in add-on is recorded in the audit log. ErrBadPlan for a plan, interval or status it does not know, a team plan with no seat or another plan with seats or the add-on; ErrNotFound for a tenant that does not exist.

func (*Store) SetRole added in v0.3.0

func (s *Store) SetRole(ctx context.Context, tenantID, userID int64, role string, now int64) error

SetRole gives one of the tenant's members role. It fails, changing nothing, with ErrNotFound for a user who is not a member, ErrBadRole, and ErrLastOwner when it would leave the tenant with no owner.

func (*Store) SetSetting

func (s *Store) SetSetting(ctx context.Context, tenantID int64, key, value string) error

SetSetting stores a tenant's JSON value under key, replacing any previous one.

func (*Store) SetSettings

func (s *Store) SetSettings(ctx context.Context, tenantID int64, values map[string]*string) error

SetSettings stores several of a tenant's settings in one transaction, so a change is never half applied. A nil value removes the key, which puts its default back in force. The keys whose value changed are recorded in the audit log by name, with the value only when it is true, false or a number: never a key, an address or a model's name.

func (*Store) SetTrashToFolder

func (s *Store) SetTrashToFolder(ctx context.Context, tenantID int64, on bool) error

SetTrashToFolder turns the tenant's trash_to_folder switch on or off.

func (*Store) Setting

func (s *Store) Setting(ctx context.Context, tenantID int64, key string) (string, error)

Setting returns one of a tenant's stored JSON values, or ErrNotFound.

func (*Store) Settings

func (s *Store) Settings(ctx context.Context, tenantID int64) (map[string]string, error)

Settings returns every setting a tenant has stored, key to JSON value.

func (*Store) SignInIdentity added in v0.2.0

func (s *Store) SignInIdentity(ctx context.Context, id Identity, now int64, seats SeatLimit) (User, error)

SignInIdentity finds the user an identity belongs to, or makes one, in one transaction. The tenant is the one of (Provider, Tenant), made when there is none yet. A new identity gets a user in that tenant who cannot sign in with a password; a known one keeps its user, whose email becomes id.Email, and a known one that was forgotten (ForgetIdentity) has its removal called off. One whose person deleted their own account, or was removed from their team, is no longer known (DeleteOwnAccount, RemoveMember), so it gets a new user. It fails, changing nothing, with ErrTenantMismatch when a known identity's user is in another tenant, and with ErrEmailInUse when another user has the email. A new tenant gets its data key at once, wrapped by the store's key provider (when it has one): a provider that cannot answer fails the sign-in with crypto.ErrKeyUnavailable, and no tenant is made.

The person who makes a tenant is its owner. A new identity joining a tenant that exists (the sign-in service let them into its organisation) gets the role of the invitation waiting for their email, which is then used up; without one they join as a member, and only while the tenant's plan has a seat free (seats; nil limits nothing), else the sign-in fails with a *SeatLimitError. An identity removed from the tenant (RemoveMember) joins it again only with an invitation, else the sign-in fails with ErrRemovedMember. Either join is recorded in the tenant's audit log.

It runs before anyone is signed in, so on Postgres it finds the tenants through the definer functions (scope.go) and scopes its transaction once it knows the tenant.

func (*Store) SpendAIEmails added in v0.3.0

func (s *Store) SpendAIEmails(ctx context.Context, tenantID int64, at time.Time, n int) error

SpendAIEmails counts n emails a model decided for the tenant on keys the operator holds at at, which no decision row records: those of a cleanup check beyond its first-cleanup grant (SpendCleanupEmail). Live sorting counts with the decision (Decision.Metered).

func (*Store) SpendCleanupEmail added in v0.3.0

func (s *Store) SpendCleanupEmail(ctx context.Context, tenantID int64, at time.Time, grant int) (fromGrant bool, err error)

SpendCleanupEmail counts one email a cleanup check had a model decide on keys the operator holds, at at: against the tenant's one-time first-cleanup grant while less than grant of it is used, else against the month's AI-sorted emails. The grant is taken in one statement, so checks running at once never take more than grant of it. fromGrant reports which it was.

func (*Store) StatsTotals

func (s *Store) StatsTotals(ctx context.Context, v Viewer, since int64) (Totals, error)

StatsTotals counts the emails of the mailboxes the viewer sees decided since `since`.

func (*Store) SummaryCost

func (s *Store) SummaryCost(ctx context.Context, v Viewer, from, to int64) (costUSD float64, calls int, err error)

SummaryCost is what the model decisions made in [from, to) on the mail of the mailboxes the viewer sees cost, and how many asked a model.

func (*Store) SummaryReview

func (s *Store) SummaryReview(ctx context.Context, v Viewer, limit int) ([]SummaryEmail, int, error)

SummaryReview lists the emails of the mailboxes the viewer sees waiting in Needs review now, newest first, at most limit of them, and how many there are.

func (*Store) SummaryRules

func (s *Store) SummaryRules(ctx context.Context, v Viewer, from, to int64) ([]RuleUse, error)

SummaryRules lists, per rule, the emails of the mailboxes the viewer sees a rule was applied to (sorted or trashed, as the Overview counts them) whose latest decision was made in [from, to), most first.

func (*Store) SummaryTrashed

func (s *Store) SummaryTrashed(ctx context.Context, v Viewer, from, to int64, limit int) ([]SummaryEmail, int, error)

SummaryTrashed lists the emails of the mailboxes the viewer sees trashed (or, in dry-run, recorded as trashed) in [from, to) and still trashed now, as the Overview counts them (outcomeSQL), newest first, at most limit of them, and how many there are. An email trashed more than once in the window is listed once, as its latest such trash left it.

func (*Store) TenantAccounts added in v0.2.0

func (s *Store) TenantAccounts(ctx context.Context, tenantID int64) ([]Account, error)

TenantAccounts lists every account of a tenant, private ones included, oldest first. It is for checks that span the tenant without showing what they find (a mailbox already connected), never for listing to a person.

func (*Store) TenantEmails added in v0.3.0

func (s *Store) TenantEmails(ctx context.Context, tenantID int64) ([]string, error)

TenantEmails lists the email addresses of a tenant's members, oldest member first, but those forgotten (ForgetIdentity) whose removal waits: who an email about the tenant's plan goes to.

func (*Store) TenantKey added in v0.3.0

func (s *Store) TenantKey(ctx context.Context, tenantID int64) (crypto.TenantKey, error)

TenantKey is the tenant's data key, made (and stored wrapped) when it has none yet.

func (*Store) TenantMembers added in v0.2.0

func (s *Store) TenantMembers(ctx context.Context, tenantID int64) (int, error)

TenantMembers counts a tenant's members, as Members lists them: a removed or forgotten person, whose removal waits, is not one (MAI-249).

func (*Store) TenantOrganisation added in v0.3.0

func (s *Store) TenantOrganisation(ctx context.Context, tenantID int64) (provider, organisation string, err error)

TenantOrganisation is the sign-in service that made the tenant and its id of the tenant's organisation there; both empty for the self-host tenant. ErrNotFound for a tenant that does not exist.

func (*Store) TenantPlan added in v0.3.0

func (s *Store) TenantPlan(ctx context.Context, tenantID, now int64) (PlanState, error)

TenantPlan returns the tenant's plan at now: the one the billing module last reported, or the trial while no plan was bought and TrialDays have not passed since the tenant was made, else ended. ErrNotFound for a tenant that does not exist.

func (*Store) Tenants added in v0.2.0

func (s *Store) Tenants(ctx context.Context) ([]int64, error)

Tenants lists every tenant's id, oldest first: what per-tenant jobs (retention) walk. Within a tenant's scope it is that tenant alone.

func (*Store) TopRules

func (s *Store) TopRules(ctx context.Context, v Viewer, since int64, limit int) ([]RuleUse, error)

TopRules lists the rules applied to the most emails of the mailboxes the viewer sees since `since`, most first.

func (*Store) TrashToFolder

func (s *Store) TrashToFolder(ctx context.Context, tenantID int64) (bool, error)

TrashToFolder reports whether the tenant's trashed mail goes to MailRules' own folder rather than the server's Trash. The executor reads it before every trash action.

func (s *Store) UnsubscribeLinks(ctx context.Context, v Viewer, address string, since int64) ([]UnsubscribeLink, error)

UnsubscribeLinks lists, for every mailbox the viewer sees that MailRules has not unsubscribed from address yet, the one-click link of the newest email from address seen since `since` that offers one. A mailbox whose mail from address offers none is not listed.

func (*Store) UpdateAccount

func (s *Store) UpdateAccount(ctx context.Context, a Account) error

UpdateAccount stores an account's label, watch folder, server (host, port and TLS mode) and status (the fields the user edits). A new server clears Module: the server is then the person's, not the module's.

func (*Store) UpdateRule

func (s *Store) UpdateRule(ctx context.Context, tenantID int64, r rules.Rule, now int64) error

UpdateRule rewrites the editable columns of one of the tenant's rules and bumps its version, so past decisions keep pointing at the wording they were made under. The author (UserID) stays as it is. A rule of another tenant is ErrNotFound.

func (*Store) UpgradeSecrets added in v0.3.0

func (s *Store) UpgradeSecrets(ctx context.Context) (int, error)

UpgradeSecrets reseals every secret still sealed under the master key itself (envelope 1, before tenant keys) under its tenant's key, and every removed mailbox's copy under the store's key-encryption key (envelope 3): those of envelope 1 and those sealed under another KEK (the master key, before a module's key service took over), tenant by tenant, each in one transaction. It returns how many it resealed; a second run reseals none. The daemon runs it at start, after the key check; until it has, envelope 1 still opens wherever a secret is read. A secret the master key does not open is left as it is (CheckSecrets counts it). Without the master key there is nothing it could open, and it does nothing.

func (*Store) UpsertContacts

func (s *Store) UpsertContacts(ctx context.Context, accountID int64, contacts []Contact) error

UpsertContacts adds addresses to an account's contacts, keeping the latest sent time.

func (*Store) Usage

func (s *Store) Usage(ctx context.Context, tenantID int64, sinceDay string) ([]UsageRow, error)

Usage lists the tenant's cost ledger from sinceDay (YYYY-MM-DD) on, oldest day first.

func (*Store) User added in v0.2.0

func (s *Store) User(ctx context.Context, id int64) (User, error)

User returns one user, or ErrNotFound.

func (*Store) UserByEmail

func (s *Store) UserByEmail(ctx context.Context, email string) (User, error)

UserByEmail looks an account up by its (case-insensitive) email.

func (*Store) UserIdentity added in v0.3.0

func (s *Store) UserIdentity(ctx context.Context, userID int64) (Identity, error)

UserIdentity is the identity a user was made from (SignInIdentity): its Provider and Subject, the rest left empty. A user with none, such as the self-host admin, is ErrNotFound.

func (*Store) Users added in v0.2.0

func (s *Store) Users(ctx context.Context) ([]User, error)

Users lists every user, oldest first, but those forgotten (ForgetIdentity) whose removal waits: who the summary email is checked for.

func (*Store) VisibleAccount added in v0.2.0

func (s *Store) VisibleAccount(ctx context.Context, v Viewer, id int64) (Account, error)

VisibleAccount returns one account the viewer sees, or ErrNotFound: one they do not see is answered as missing.

func (*Store) VisibleAccounts added in v0.2.0

func (s *Store) VisibleAccounts(ctx context.Context, v Viewer) ([]Account, error)

VisibleAccounts lists the accounts the viewer sees, oldest first.

func (*Store) VisibleAction added in v0.2.0

func (s *Store) VisibleAction(ctx context.Context, v Viewer, id int64) (Action, error)

VisibleAction returns one action on a mailbox the viewer sees, or ErrNotFound.

func (*Store) VisibleBatch added in v0.2.0

func (s *Store) VisibleBatch(ctx context.Context, v Viewer, id int64) (Batch, error)

VisibleBatch returns one batch the viewer sees, or ErrNotFound.

func (*Store) VisibleBatchActions added in v0.2.0

func (s *Store) VisibleBatchActions(ctx context.Context, v Viewer, batchID int64) ([]Action, error)

VisibleBatchActions lists a batch's actions on the mailboxes the viewer sees, oldest first. A tenant's batch without a mailbox (the day's live batch, a correction or an undo) can hold actions on mailboxes of several people; each sees only theirs and shared ones.

func (*Store) VisibleMessage added in v0.2.0

func (s *Store) VisibleMessage(ctx context.Context, v Viewer, id int64) (Message, error)

VisibleMessage returns one message of a mailbox the viewer sees, or ErrNotFound.

func (*Store) WaitingDrafts added in v0.3.0

func (s *Store) WaitingDrafts(ctx context.Context, now int64, limit int) ([]DraftJob, error)

WaitingDrafts lists up to limit draft jobs due by now, oldest first.

func (*Store) WaitingMoves added in v0.3.0

func (s *Store) WaitingMoves(ctx context.Context, now int64, limit int) ([]Move, error)

WaitingMoves lists the person's moves the module has not taken yet that are due at now, oldest first: at most limit.

func (*Store) WaitingNotices added in v0.3.0

func (s *Store) WaitingNotices(ctx context.Context, now int64, limit int) ([]Notice, error)

WaitingNotices lists up to limit notices the module has not taken yet that are due by now, oldest first.

func (*Store) WaitingRemovedMailboxes added in v0.3.0

func (s *Store) WaitingRemovedMailboxes(ctx context.Context, now int64, limit int) ([]RemovedMailbox, error)

WaitingRemovedMailboxes lists at most limit removed mailboxes due to be handed over by now, the longest waiting first.

func (*Store) WaitingSeatChecks added in v0.3.0

func (s *Store) WaitingSeatChecks(ctx context.Context, now int64, limit int) ([]SeatCheck, error)

WaitingSeatChecks lists at most limit seat checks due by now, the longest due first.

func (*Store) WithKeyring added in v0.3.0

func (s *Store) WithKeyring(k *Keyring) *Store

WithKeyring is the store sealing and opening secrets with k. It shares the database of s.

func (*Store) WithKeys added in v0.3.0

func (s *Store) WithKeys(p crypto.KeyProvider) *Store

WithKeys is the store sealing and opening secrets with p's key-encryption key, on a Keyring of its own that keeps keys DefaultTenantKeyCache. It shares the database of s.

type Summary

type Summary struct {
	ID            int64
	UserID        int64
	DueAt         int64
	Status        string
	Attempts      int
	LastAttemptAt int64
	PeriodStart   int64 // 0 until sent
	PeriodEnd     int64
	SentAt        int64
}

Summary is one row of summaries: a scheduled summary email.

type SummaryEmail

type SummaryEmail struct {
	MessageID int64
	FromName  string
	FromAddr  string
	Subject   string
	Folder    string // a trashed email: the folder the trash moved it to; "" = the server's Trash
	DryRun    bool   // a trashed email: the trash was only recorded, in dry-run
}

SummaryEmail is one email as a summary lists it: who sent it, its subject and, for a trashed one, where it went. Never its text.

type Totals

type Totals struct {
	Processed    int // emails decided
	Sorted       int // of those, a rule or sender rule was applied (or recorded, in dry-run) and not undone since
	WithoutModel int // of those, a sender rule or condition rule acted on, with no model asked; never more than Processed
	Trashed      int // emails with a trash action that is in effect (or recorded, in dry-run)

	// Where the processed emails ended up; the four add up to Processed.
	WentSorted  int // a rule was applied and it did not trash the email
	WentTrash   int // a rule was applied and it trashed the email
	WentReview  int // waiting in Needs review
	WentNowhere int // left in the inbox: no rule matched, it was the mailbox's own mail, it could not be handled, or all that was done to it was undone
}

Totals are the counts of the stats tiles.

type UnsubscribeLink struct {
	AccountID int64
	URL       string
}

UnsubscribeLink is the one-click unsubscribe link of the newest email from an address on one mailbox.

type UsageRow

type UsageRow struct {
	Day       string // YYYY-MM-DD, UTC
	Provider  string
	Model     string
	Purpose   string // decide | escalate | compose | test | cleanup | suggest | draft
	Operator  bool   // made with a key the operator holds (MAILRULES_MODE=cloud)
	Calls     int
	TokensIn  int
	TokensOut int
	CostUSD   float64
}

UsageRow is one row of the cost ledger.

type User

type User struct {
	ID           int64
	Email        string
	PasswordHash string
	CreatedAt    int64
	TenantID     int64
	Role         string // RoleOwner, RoleAdmin or RoleMember
}

User is a person who signs in. Self-host has one, the admin, in SelfHostTenant.

func (User) Viewer added in v0.2.0

func (u User) Viewer() Viewer

Viewer is the user as the one who sees through it.

type Viewer added in v0.2.0

type Viewer struct {
	UserID, TenantID int64
}

Viewer is the signed-in person a user-facing read or change is made for. They see an account when it is in their tenant and they own it or it is shared (visibleAccounts), and through it its messages, decisions, actions, corrections, contacts, folders, batches, stats, activity and Needs review queue. Rules, sender rules, settings and the usage ledger belong to the tenant.

Directories

Path Synopsis
Package migrations holds the store's schema migrations, one set per SQL dialect, embedded into the binary and applied by goose.
Package migrations holds the store's schema migrations, one set per SQL dialect, embedded into the binary and applied by goose.
Package pgtest gives a test its own Postgres database, migrated to the store's current Postgres schema, with connections as the roles the hosted build runs as.
Package pgtest gives a test its own Postgres database, migrated to the store's current Postgres schema, with connections as the roles the hosted build runs as.
Command querycover fails when a statement of the store was never run by the tests behind a coverage profile.
Command querycover fails when a statement of the store was never run by the tests behind a coverage profile.
Package storetest gives tests outside the store a fresh, migrated database, so that the backend a test runs against is chosen in one place rather than in every test file: Postgres when MAILRULES_TEST_POSTGRES_DSN is set (see pgtest), SQLite otherwise.
Package storetest gives tests outside the store a fresh, migrated database, so that the backend a test runs against is chosen in one place rather than in every test file: Postgres when MAILRULES_TEST_POSTGRES_DSN is set (see pgtest), SQLite otherwise.

Jump to

Keyboard shortcuts

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