← All posts

Vocion can mail you now, and the first thing it mails is the daily team report

Release notes 2026-09-15 — outbound mail over Resend and the daily-team-report job (vocion-core#324, merged): what changed, what needs you, are we on track, what happens next — in your inbox, and as a Briefing in the app.

Vocion Teamvocion-v2.69.0

Shipped: vocion-core#324, merged to main on 2026-09-15. Part of the 2026-09-15 release notes.

The outcome

A report nobody opens is not a report. Vocion already published team briefings to /dashboard/briefings every morning; the person they were for was in their email. This release puts the read where the reader is, and orders it the way a manager reads: What changed? What needs me? Are we on track? What happens next? Then the per-team table. Then, last, tokens and cents as evidence.

What it is

Outbound mail. packages/core/src/libs/mail/ — sendMail({ to, subject, html, text }) over a Resend transport using plain fetch. Off by default. The flag pattern matches external workers: VOCION_MAIL_ENABLED=1 turns it on, RESEND_API_KEY and VOCION_MAIL_FROM configure it, and with the flag unset sendMail returns { skipped: true } instead of throwing.

The daily-team-report job — the first built-in do: { job: … } an automation can run (packages/core/src/services/jobs/dailyTeamReport.ts). It folds the trailing window of worker_run rows into team → member stats, counts what is waiting on a human, pulls the latest briefing, renders one HTML mail, stores the same content as a Briefing row so the report is readable in the app even with mail disabled, then sends. Markdown and HTML render from one set of sections (services/reports/renderDailyTeamReport.ts), so the in-app copy and the mail cannot drift.

A follow-up in the same PR lets a workspace mail a team's own briefing in full rather than the workspace rollup: input.briefing: { teamSlug }. The subject becomes that briefing's title.

How a workspace opts in

# automations/daily-team-report.yaml
slug: daily-team-report
name: Daily team report
agent: ceo
when:
  schedule: "0 13 * * *"     # 13:00 UTC
do:
  job: daily-team-report
  input:
    hours: 24
    # to: [ops@example.com]   # default: the workspace accountableUser

# automations/morning-briefing-email.yaml — mail one team's briefing
do:
  job: daily-team-report
  input:
    briefing:
      teamSlug: revops

Automations are scheduled through Temporal; the job itself is deterministic server code, not an agent turn.

Setup, honestly

Sending needs a domain verified in Resend — DKIM, SPF and (optionally) MX records at your DNS host. Until the domain verifies, the first send fails with a provider error and the Briefing row still lands. The guide walks through it: docs/guides/email.md. A manual trigger exists for testing: npm run report:daily -- --org <id> [--to a@b] [--no-mail] [--html out.html].

For tenants

No behaviour changes until a deployment sets the flag and a workspace ships the automation. With the automation and no flag: one Briefing per fire, no mail.

The screenshot is the Briefings page on a local workspace with fictional data.

Where this lives in the repo

Failure modes: with the flag on and the domain unverified, Resend answers 403 and sendMail raises a MailError with code PROVIDER; the briefing row is written before the send, so the report is never lost. There is no retry queue in this release — the next scheduled fire is the retry.