---
title: Aggregate account observations and capture
description: Query named existing views across routed accounts without confusing their units or creating a global history.
---

Use named aggregates when your application needs one read surface over several existing account views. They sample configured observation/capture stores; they do not submit messages or create durable history.

## Aggregate minimal observations

```ts
import { Effect, Layer } from "effect";
import { EmailRouter, emailLayer, observedEmailRouterLayer } from "@corven/email-sdk";
import {
  EmailObservationAggregate,
  observedEmailSenderLayer,
} from "@corven/email-sdk/observations";
import { recordingSenderLayer } from "@corven/email-sdk/testing";

const first = emailLayer().pipe(
  Layer.provideMerge(observedEmailSenderLayer(Layer.fresh(recordingSenderLayer), { capacity: 2 })),
);

const second = emailLayer().pipe(
  Layer.provideMerge(observedEmailSenderLayer(Layer.fresh(recordingSenderLayer), { capacity: 3 })),
);

const router = observedEmailRouterLayer(
  [
    { layer: first, name: "first" },
    { layer: second, name: "second" },
  ],
  { primary: "first" },
);

export const fictionalObservationAggregation = Effect.gen(function* queryNamedObservations() {
  const route = yield* (yield* EmailRouter).select({ primary: "second" });
  yield* route.send({
    body: { text: "A fictional fact aggregation example." },
    from: "sender@fictional.invalid",
    subject: "Fictional notification",
    to: "recipient@fictional.invalid",
  });

  return yield* (yield* EmailObservationAggregate).snapshot;
}).pipe(Effect.provide(router));
```

`observedEmailRouterLayer` keeps named account views available through `EmailObservationAggregate`. Each source identifies its unit: `SenderInvocation` or `NativeOperation`. Retained counts and truncation describe the samples, not lifetime sends or deliveries.

## Aggregate private capture

```ts
import { Effect, Layer } from "effect";
import { Email, emailLayer, capturedEmailRouterLayer } from "@corven/email-sdk";
import { EmailCaptureAggregate, capturedEmailSenderLayer } from "@corven/email-sdk/capture";
import { recordingSenderLayer } from "@corven/email-sdk/testing";

const options = {
  capacity: 100,
  maxBytes: 1_048_576,
  privateData: "AuthorizePayloadCapture",
} as const;

const primary = emailLayer().pipe(
  Layer.provideMerge(capturedEmailSenderLayer(Layer.fresh(recordingSenderLayer), options)),
);

const backup = emailLayer().pipe(
  Layer.provideMerge(capturedEmailSenderLayer(Layer.fresh(recordingSenderLayer), options)),
);

const router = capturedEmailRouterLayer(
  [
    { layer: primary, name: "primary" },
    { layer: backup, name: "backup" },
  ],
  { fallbacks: ["backup"], primary: "primary" },
);

export const fictionalAggregateCapture = Effect.gen(function* inspectFictionalAccounts() {
  yield* (yield* Email).send({
    body: { text: "A fictional recording example." },
    from: "sender@fictional.invalid",
    subject: "Fictional aggregate capture",
    to: "recipient@fictional.invalid",
  });

  return yield* (yield* EmailCaptureAggregate).snapshot;
}).pipe(Effect.provide(router));
```

`capturedEmailRouterLayer` supplies `EmailCaptureAggregate` over authorized capture views. Read `retainedRecords`, `retainedBytes`, source snapshots and `truncated`; keep payloads private.

## Keep acquisition and failure semantics

Reuse the same Layer values used by sending so queries observe the same acquired stores. Source names must be distinct bounded identifiers, not private account values. Aggregation returns its exported aggregate error if a sample cannot be read or validated.

Samples across sources are not globally atomic. Missing data, eviction or a closed view does not prove non-submission and must not reset route/key recovery evidence.
