package main // Hearing mesh-mail's added events (novox/hq ADR 0459 §3, §12, issue 1518): mesh-mail-reader consumes // mesh-mail.mail.*.message.added under its own runs-as account, exactly as mesh-mail-notifier does (main.go // AddedPattern, watchWith), but records each one as a waiting mail (store.go, pgstore.go) BEFORE the handler // returns success — so a mail heard is never lost, even across a restart: the controller composes a durable // consumer (MaxDeliver 5, AckWait 30s) that redelivers an event five times before it is dead-lettered, and a // store failure here must make that redelivery happen, never swallow the event. import ( "context" "encoding/json" "time" ) // EventBody is what mesh-mail's added event says of a message (metadata only; mailevent.go names where it is // said). Unknown fields are ignored: the reader reads only what it needs, never a body. type EventBody struct { Account string `json:"account"` Folder string `json:"folder"` UID uint32 `json:"uid"` UIDValidity uint32 `json:"uidvalidity"` MessageID string `json:"message-id"` Date string `json:"date"` From []Person `json:"from"` Subject string `json:"subject"` Size int64 `json:"size"` Flags []string `json:"flags"` } // Hear records one added event as a waiting mail (1518/AC2): the store is written to before this returns // nil, so the caller (the bus subscription's handler) only then acknowledges the event. A store failure is // returned unchanged, so the event is redelivered rather than lost (1518/AC2). The same event heard twice // (1518/AC3) is recorded once: AddWaiting itself is idempotent on the mail's own identity. func Hear(ctx context.Context, store Store, raw json.RawMessage, now func() time.Time) error { var body EventBody if err := json.Unmarshal(raw, &body); err != nil { // An event this reader cannot even read is taken and dropped, exactly as mesh-mail-notifier does // (main.go watchWith): a malformed event would otherwise be redelivered forever, never succeeding. return nil } date, err := time.Parse(time.RFC3339, body.Date) if err != nil { date = now() } w := WaitingMail{ Account: body.Account, Folder: body.Folder, MessageID: body.MessageID, UIDValidity: body.UIDValidity, UID: body.UID, Date: date, From: body.From, Subject: body.Subject, Flags: body.Flags, Size: body.Size, Heard: now(), } return store.AddWaiting(ctx, w) } // AddedPattern is the mesh-mail stream's event this module consumes (its own `consumes` entry, novox/hq ADR // 0459 §3): a new message's `added` only. const AddedPattern = "mesh-mail.mail.*.message.added"