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
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
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:
- Verify
request.auth(user must be signed in) - Validate RBAC permissions (SM, PM, or Owner)
- Initialize Postmark client from secret
- Send emails using Postmark templates
- Log to
emailHistorysubcollection
| Function | Template Alias | Trigger Source |
|---|---|---|
sendCallSheet | call-sheet | EmailTab |
sendRehearsalReport | rehearsal-report | Notes Page |
sendLineNotes | line-notes | Notes Page |
sendDeadlinePing | deadline-ping | EmailTab / PingModal |
sendMeetingInvites | meeting-invitation | EmailTab |
sendProjectInvite | project-invite | EmailTab |
sendCustomAnnouncement | custom-announcement | EmailTab |
sendBatchInvitations | batch-invitations | Admin 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 emailHistorydedupeKeyfield — enableswasRecentlySentdeduplication- 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.
| Property | Value |
|---|---|
| Trigger | onDocumentCreated |
| Path | projects/{projectId}/members/{uid} |
| Template | welcome-onboarding |
| Dedup Window | 5 minutes |
notifyScheduleChange
Notifies attendees when a rehearsal event's date, time, or location changes.
| Property | Value |
|---|---|
| Trigger | onDocumentWritten |
| Path | projects/{projectId}/rehearsalSchedule/{eventId} |
| Template | schedule-change |
| Filters | Skips deadlines, skips creates/deletes, only fires on date/time/location changes |
| Recipients | Resolved from attendees[] via resolveAttendeeUids |
notifyDeadlineApproaching
Daily scan for pending deadlines due within 48 hours.
| Property | Value |
|---|---|
| Trigger | onSchedule |
| Schedule | every day 09:00 (America/New_York) |
| Template | deadline-approaching |
| Dedup Window | 24 hours |
| Urgency Levels | overdue, due today, due tomorrow |
notifyProfileReminder
Weekly check for incomplete personnel profiles.
| Property | Value |
|---|---|
| Trigger | onSchedule |
| Schedule | every monday 10:00 (America/New_York) |
| Template | profile-reminder |
| Dedup Window | 7 days |
| Checked Fields | Phone, Email, Emergency Contact, Emergency Phone, Allergies |
notifyMentionPost / notifyMentionComment
Notifies users when they are @mentioned in a feed post or comment.
| Property | Value |
|---|---|
| Trigger | onDocumentCreated |
| Paths | projects/{projectId}/feed/{postId}, projects/{projectId}/feedComments/{commentId} |
| Template | mention-notification |
| Mention Resolution | user type → direct UID; department type → resolved via data/cast shard |
| Exclusions | Author is excluded (no self-notifications) |
Email History
All emails (manual and automated) are logged to:
projects/{projectId}/emailHistory/{docId}Schema
{
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.htmlAll 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:
// 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