Cosmo Docs

Weekly Digest

Your Monday summary of what moved across the org.

The Weekly Digest is a summary of activity across your workspaces, delivered by email every Monday and available any time in the Hub under Weekly Digest.

Every digest is built per person. Two people in the same organisation get different digests, because each one only ever covers what that person can already see in Cosmo.

Cadence

Period coveredThe previous complete week — Monday 00:00 to Sunday 23:59 UTC
SentMondays, 08:00 UTC
WhereEmail, plus the Weekly Digest tab in the Hub

Quiet weeks are skipped. If nothing happened that you can see, no email is sent — we would rather stay out of your inbox than send you an empty summary.

What's in it

Documents

Artifacts created or edited during the week, with the workspace they live in. New ones are flagged.

Tasks

What was completed, how many were created, plus any of your own tasks that are now overdue.

Shared with you

Workspaces and documents other people shared with you during the week.

New workspaces

Workspaces created in your teams.

Your conversations

How much you worked with the AI, and the titles of your own recent chats.

New integrations

Connectors newly linked for the organisation.

What's deliberately not in it

The digest is an email, so it is written to be safe to sit in an inbox. It never contains:

  • The contents of any document or chat — titles only, never bodies.
  • Anything from a team you are not a member of.
  • Anyone else's personal drafts. Your own personal-scope artifacts appear in your digest and nobody else's.
  • Anyone else's chat titles.
  • Other people's email addresses. Colleagues are named by display name; when someone has no name set, they appear as "A teammate".
  • Another member's personal integrations. Only organisation-wide connectors and your own appear.
  • Token usage or cost figures.

Turning it off

Every digest email has an Unsubscribe link in the footer, and supports one-click unsubscribe in Gmail, Outlook and Apple Mail. Unsubscribing:

  • takes effect immediately and needs no sign-in,
  • applies per organisation — opting out of one org's digest leaves any others running,
  • keeps working even after you leave the organisation,
  • does not turn off the in-app digest. The Weekly Digest tab in the Hub keeps working, because opting out of email is not the same as opting out of the feature.

The confirmation page offers an Undo if you clicked by mistake — useful because some email clients and security scanners follow links automatically, so an opt-out can happen without you clicking anything. The Undo offer expires about 30 minutes after the page is opened; after that, resubscribe from Cosmo under Settings.

Notification preferences are stored per person, per organisation, and cover three independent channels: the weekly digest, re-engagement emails, and product emails. Opting out of one leaves the others untouched.

AI summary line

When enabled for your organisation, the digest opens with a one-sentence summary of the week. It is generated from the counts only — the model never sees your document titles or chat content. If the summary cannot be generated, the digest sends without it, with every section intact.

Self-hosted configuration

VariablePurpose
DIGEST_CRON_ENABLEDtrue to enable the Monday scheduled run. Off by default.
DIGEST_ENABLED_ORG_IDSComma-separated org ids the scheduler may mail. Empty means none.
DIGEST_AI_SUMMARYtrue to add the AI summary line. Off by default.
DIGEST_HEADLINE_MODELModel for the summary line. Defaults to claude-sonnet-4-6.
NOTIFICATION_SECRETSigns unsubscribe links. Falls back to CLERK_SECRET_KEY. Rotating it invalidates unsubscribe links in already-delivered mail.
COSMO_WEB_URLBase URL used for links in the email. Must be set, or digest links will not resolve.
BREVO_API_KEYBrevo API key. Without it, digests are computed and stored but never dispatched.
EMAIL_FROM_ADDRESSSender address. Must be on a domain verified/authenticated in Brevo, or sends to real members will fail or land in spam.
BREVO_API_URLOverrides Brevo's endpoint. Leave unset in any real deployment — see below.

The scheduler is an in-process timer. On Azure App Service, enable Always On or the app can be unloaded while idle and silently skip the Monday run. As an alternative, drive it from an external scheduler against POST /api/v1/admin/digest/send.

Verifying the digest locally

Brevo enforces an account-level IP allowlist, so a development machine usually cannot reach the API at all — every send returns unauthorized naming the caller's address, which is indistinguishable from a broken digest. To exercise the real send path without Brevo, point the mailer at a local capture server:

pnpm -C packages/server mail:capture          # listens on 8025

Then in packages/server/.env:

BREVO_API_URL=http://localhost:8025/v3/smtp/email
DIGEST_CRON_ENABLED=true
DIGEST_ENABLED_ORG_IDS=<your org id>

Restart the server — it logs Weekly digest armed — and trigger a run:

curl -X POST http://localhost:4000/api/v1/admin/digest/send   -H "Authorization: Bearer $CLERK_SECRET_KEY" -H "Content-Type: application/json"   -d '{"orgId":"<your org id>","force":true}'

Each message is written to packages/server/.mail-capture/ as an .html file you can open plus the exact request body as .json. This is the same code path production uses — same fetch, same body — with only the destination changed, so a message that renders here is the message Brevo would have been given.

Delivering to a real inbox

mail:capture never sends anything. To actually receive the digest during development, run the relay instead — it accepts the same request and forwards it over SMTP:

pnpm -C packages/server mail:relay

The easiest target is MailDev — a local inbox with a web UI, needing no account and no credentials:

npx maildev --smtp 1025 --web 1080

Then in packages/server/.env:

DEV_SMTP_HOST=localhost
DEV_SMTP_PORT=1025

Mail sent by the app appears at http://localhost:1080 in a normal-looking inbox, which is the closest thing to checking a real mail client without one.

To deliver to an actual mailbox instead, point the same variables at a real server and add credentials (gitignored — never commit them, and never pass them on the command line where they reach shell history):

DEV_SMTP_HOST=smtp.gmail.com
DEV_SMTP_PORT=587
DEV_SMTP_USER=you@example.com
DEV_SMTP_PASS=<app password, not your account password>

Credentials are optional: with a host but no user/pass the relay connects unauthenticated, which is what a local sink expects. With no host at all it falls back to an Ethereal throwaway and prints a preview URL per message.

SMTP lives in this script and never in MailerService, which keeps exactly one egress point speaking one request shape. nodemailer is a devDependency; no product code imports it.

BREVO_API_URL must be unset in any deployed environment. It is logged loudly at startup when overridden, because a stray value silently swallows every send.

Checking why a digest did not arrive

Every gate above defaults closed, so a deployment that never set them mails nobody and looks entirely healthy doing it. The server reports its own state:

curl -H "Authorization: Bearer $CLERK_SECRET_KEY"   "https://your-api-host/api/v1/admin/digest/status"

config.armed is true only when a Monday run would actually send. When it is false, config.blockers names each unmet precondition by environment variable. lastRun covers the most recent complete week — pass ?periodStart=YYYY-MM-DD for an earlier one — and separates the two failures that look identical from an empty inbox:

  • An org with ran: false recorded nothing at all for the week. Usually that means the run never reached it — but it is also what an org whose every recipient's payload failed to build looks like, because a failed build releases its claim and deletes the row. The per-recipient errors are in the server log (Digest build failed for <userId>), and the run's own summary line reports failed=N.
  • An org with FAILED in byStatus did run, and lastRun.failures carries the mail provider's own rejection text for each recipient.

The same summary is written to the log on every startup, so a disarmed digest is visible from the boot output rather than only from a warning emitted at 08:00 on a Monday.