Skip to content

Email API

Production email distribution — Manual and Automated Cloud Functions.


Overview

The email system uses Postmark for transactional email delivery. All functions are Firebase Cloud Functions (v2) that use the POSTMARK_SERVER_TOKEN secret for authentication.

  • Manual functions (onCall) are triggered by Stage Managers from the UI
  • Automated functions (onDocumentCreated, onDocumentWritten, onSchedule) fire in response to data changes or scheduled intervals

Shared Module: postmark-client.ts

All email functions import from a shared utility module at functions/src/email/postmark-client.ts.

Core Utilities

typescript
import {
  createPostmarkClient,  // Lazy-init Postmark SDK from POSTMARK_SERVER_TOKEN
  buildLayoutVars,       // Build standard template layout variables
  getSenderEmail,        // Look up sender's email from Auth (for ReplyTo)
  validateEmailSender,   // RBAC check — SM/PM/Owner only
  FROM_EMAIL,            // 'notifications@on-book.app'
} from './postmark-client';

Phase 2 Automation Utilities

typescript
import {
  filterOptedOutByUid,   // Filter UIDs with emailOptOut: true
  wasRecentlySent,       // Dedup guard — check emailHistory for recent sends
  resolveAttendeeUids,   // Personnel IDs + group:* tags → Auth UIDs
  getProjectLayoutVars,  // Convenience: read project doc + build layout vars
  getEmailsForUids,      // Batch Auth lookup → Map<uid, email>
} from './postmark-client';

Manual Functions (Phase 1)

All manual functions follow this pattern:

  1. Verify request.auth (user must be signed in)
  2. Validate RBAC permissions (SM, PM, or Owner)
  3. Initialize Postmark client from secret
  4. Send emails using Postmark templates
  5. Log to emailHistory subcollection
FunctionTemplate AliasTrigger Source
sendCallSheetcall-sheetEmailTab
sendRehearsalReportrehearsal-reportNotes Page
sendLineNotesline-notesNotes Page
sendDeadlinePingdeadline-pingEmailTab / PingModal
sendMeetingInvitesmeeting-invitationEmailTab
sendProjectInviteproject-inviteEmailTab
sendCustomAnnouncementcustom-announcementEmailTab
sendBatchInvitationsbatch-invitationsAdmin Portal

Automated Functions (Phase 2)

Automated functions differ from manual ones:

  • No request.auth — triggered by Firestore/CRON, not user calls
  • sentBy: 'system' — identifies automated sends in emailHistory
  • dedupeKey field — enables wasRecentlySent deduplication
  • No RBAC validation — system-triggered functions don't have a calling user

notifyWelcome

Sends a welcome email when a new member is added to a project.

PropertyValue
TriggeronDocumentCreated
Pathprojects/{projectId}/members/{uid}
Templatewelcome-onboarding
Dedup Window5 minutes

notifyScheduleChange

Notifies attendees when a rehearsal event's date, time, or location changes.

PropertyValue
TriggeronDocumentWritten
Pathprojects/{projectId}/rehearsalSchedule/{eventId}
Templateschedule-change
FiltersSkips deadlines, skips creates/deletes, only fires on date/time/location changes
RecipientsResolved from attendees[] via resolveAttendeeUids

notifyDeadlineApproaching

Daily scan for pending deadlines due within 48 hours.

PropertyValue
TriggeronSchedule
Scheduleevery day 09:00 (America/New_York)
Templatedeadline-approaching
Dedup Window24 hours
Urgency Levelsoverdue, due today, due tomorrow

notifyProfileReminder

Weekly check for incomplete personnel profiles.

PropertyValue
TriggeronSchedule
Scheduleevery monday 10:00 (America/New_York)
Templateprofile-reminder
Dedup Window7 days
Checked FieldsPhone, Email, Emergency Contact, Emergency Phone, Allergies

notifyMentionPost / notifyMentionComment

Notifies users when they are @mentioned in a feed post or comment.

PropertyValue
TriggeronDocumentCreated
Pathsprojects/{projectId}/feed/{postId}, projects/{projectId}/feedComments/{commentId}
Templatemention-notification
Mention Resolutionuser type → direct UID; department type → resolved via data/cast shard
ExclusionsAuthor is excluded (no self-notifications)

Email History

All emails (manual and automated) are logged to:

projects/{projectId}/emailHistory/{docId}

Schema

typescript
{
  templateAlias: string;      // e.g., 'schedule-change'
  subject: string;            // Email subject line
  recipientCount: number;     // Total recipients
  sentBy: string;             // Auth UID or 'system'
  sentAt: string;             // ISO timestamp
  dedupeKey?: string;         // For wasRecentlySent dedup (automated only)
  sent: number;               // Successfully sent count
  total: number;              // Total attempted
}

Postmark Templates

Templates are managed in the Postmark Dashboard. Reference HTML files are maintained locally at:

functions/src/email/templates/
├── schedule-change.html
├── deadline-approaching.html
├── welcome-onboarding.html
├── profile-reminder.html
└── mention-notification.html

All templates inherit from the on-book-master Layout, which provides:

  • Production logo and title in the header
  • Prussian blue header bar (#1B3A4B)
  • Amber CTA buttons (#D97706)
  • "Powered by On Book Pro" footer badge
  • Unsubscribe/preferences link

Deployment

All email functions must be re-exported from functions/src/index.ts:

typescript
// Phase 1 — Manual
export { sendCallSheet } from './email/send-call-sheet';
export { sendMeetingInvites } from './email/send-meeting-invites';
// ... etc

// Phase 2 — Automated
export { notifyWelcome } from './email/notify-welcome';
export { notifyScheduleChange } from './email/notify-schedule-change';
export { notifyDeadlineApproaching } from './email/notify-deadline-approaching';
export { notifyProfileReminder } from './email/notify-profile-reminder';
export { notifyMentionPost, notifyMentionComment } from './email/notify-mention';

Last updated: February 2026