Mail Agent

A mailbox-access facade that owns RBAC, posture enforcement, audit emission, dispatch (local / worker-pool / queue), and worker isolation around a mail store, so a protocol server built on top can stay a thin shell. It is designed to be the shared dispatch layer mail-protocol servers route through; today the read surface and the mailbox-mutation + Sieve-upload methods are wired, while the compose/send and identity/vacation/MDN/export verbs are not yet wired into the facade (see below).

agent.create() returns the facade. Methods backed by b.mailStore (folders / fetch / search / move / flag / delete / expunge, plus sieve.put) run immediately. The remaining verbs — compose / send / reply / forward, sieve.list / sieve.activate, identity / vacation / mdn.*, export / job / import — throw mail-agent/not-implemented: they are not yet routed through the agent. Until they are, compose the underlying primitive directly (b.mail.send.deliver for outbound, b.mail.sieve for Sieve, b.mailMdn for MDN, etc.) — which is what the framework's own JMAP emailSubmissionSet handler does. They wire into the facade when a protocol server adopts the agent as its dispatch layer.

var agent = b.mail.agent.create({
  store, audit, permissions,
  posture: "hipaa",
  identity: function (actorId) {
    return { email: actorId + "@hospital.example", name: actorId };
  },
  dispatch: { mode: "auto" },
});

var folders = await agent.folders({ actor: { id: "u1", roles: ["clinician"], purposeOfUse: "TREATMENT" } });

## Dispatch modes

- local (default when no queue) — every method runs in-process. Fast-path ops (fetch / folders / flag / quota) bypass worker dispatch; heavy ops (search / export / sieve-on-bulk) run on the supplied workerPool when configured. - queue — every method publishes to the queue topic; an agent.consumer() running in a dedicated process (or replicas across hosts) pulls and executes. The consumer carries its own store reference; the queue payload carries actor + posture metadata, which the consumer re-validates against its local posture before unseal (no posture downgrade across the boundary). - auto — fast-path ops local, heavy ops to queue if configured else workerPool else local.

## Posture enforcement

When posture is set, every actor passed to every method must carry the posture-required fields (HIPAA → purposeOfUse, PCI-DSS → pciScope, GDPR → lawfulBasis). b.guardMailQuery. validateActor is the canonical check; the agent invokes it on every entrypoint.

b.mail.agent.create(opts) #

stable0.9.20
{
  store:        b.mailStore instance,    // required
  audit:        b.audit namespace,        // optional; defaults to b.audit
  permissions:  b.permissions instance,   // optional; agent skips RBAC if absent (operator's choice)
  posture:      "hipaa"|"pci-dss"|"gdpr"|"soc2"|null,
  identity:     function(actorId) → { email, name }   // OR object map
  dispatch:     { mode, queue, workerPool, queueTopic, taskTimeoutMs, queueDepthCap, vaultKeyDelivery },
}

Create the agent facade. Returns an object with read / write / sieve / identity / mdn / export / import methods. Reads stay synchronous-shaped via promises; writes audit on completion. (The queue consumer is the sibling export b.mail.agent.consumer, not a method on this object.)

var agent = b.mail.agent.create({ store: myStore });
var folders = await agent.folders({ actor: { id: "u1" } });

b.mail.agent.consumer(opts) #

stable0.9.20
{
  agent:       a b.mail.agent.create() instance,   // required
  queue:       b.queue / b.queueRedis,             // required
  taskTopic:   string,                              // default "mail.agent.tasks"
  maxConcurrency: number,                           // default 4
}

Create a queue consumer that pulls mail.agent.tasks envelopes and runs them against an operator-supplied agent. Each replica runs in its own process / host for multi-host load-spreading; queue payload carries actor + posture; consumer re-validates against its local posture before unseal.

var consumer = b.mail.agent.consumer({ agent: localAgent, queue: redisQueue });
await consumer.start();

Last updated 2026-08-08T16:39:49.652Z by seeder.