Auburn MyGov Docs Gitea

Layer

Distribution & Publishing

The presentation layer. Where and how records become visible — to the public via the CMS/WebPages, to subscribers via eNotifier, and to developers via embeddable web components. Every module has operational data; this layer decides which surfaces that data reaches and what form it takes when it gets there.

Status Draft
Last updated
Source docs Consolidated from contact-surface.html and enotifier.html (now redirects)
Related LayerLocation · Calendar & Time
PlatformWorkflow Engine · Microsoft Platform
ModuleReservations · Programs & Registration · Forms

Scope of This Layer

Distribution and Publishing is the answer to: "I have data in the system — how does it become visible or send a notification?" The question applies independently of which module owns the data. A Parks program, a reservation confirmation, a job posting, and a board meeting agenda all need to be published somewhere. This layer defines how that happens without requiring each module to invent its own CMS integration.

Channel 1
CMS / WebPages

The public website. CMS pages reference operational data via contact records (Org → CMS bridge). Staff control what appears and how via the CMS admin.

Channel 2
eNotifier

Email distribution to subscriber interest lists. Staff-composed bulk email with opt-in push automation for transactional notifications.

Channel 3
Web Components

Embeddable JavaScript widgets that surface live MyGov data (calendars, program lists, news feeds) on any page — internal or external.

Channel 4
Transactional Email

System-triggered single-recipient emails: reservation confirmations, registration receipts, form submission acknowledgments. Delivered via Workflow Engine / MSGraph.

Org → CMS Bridge: The Entity / Contact Pattern

The operational layer (organization schema) holds the source of truth for departments, facilities, spaces, programs, and staff. The CMS layer (cms schema) holds what is displayed to the public. These are deliberately separate — operational data changes often; public presentation changes less often and needs editorial control.

The bridge is a Contact record: a CMS-layer entity (e.g. FacilityContact) that carries display-layer overrides of its operational source (e.g. Facility). A COALESCE-style pattern resolves the display value: COALESCE(contact.nameOverride, facility.name) — the override wins if set, otherwise the operational field feeds through.

This "seed and override" approach means:

  • New entities get a Contact record automatically (seeded from operational data).
  • Staff never have to re-enter data they already entered in the operational module.
  • CMS display can diverge from operational data intentionally (marketing copy, hours notes) without corrupting the source record.
  • The operational record remains clean and queryable; CMS fields do not leak into it.

Pattern Coverage

Org entityCMS contactPattern
organization.Departmentcms.DepartmentContact1-to-1, seed on create
organization.Employeecms.EmployeeContact1-to-1, opt-in (not all staff are public)
organization.Facilitycms.FacilityContact1-to-1, seed on create
organization.FacilitySpacecms.FacilitySpaceContact1-to-1, seed on create; display only
data.Programcms.ProgramContact1-to-1, seed on publish
data.News(native CMS — no bridge needed)CMS-native; no operational source

FacilitySpace — Three-Entity Model

FacilitySpace is the only entity with three layers: spatial identity, public display, and booking policy. Each belongs to its own record.

EntitySchemaPurposeKey fields
FacilitySpace organization Spatial identity — what the space is, canonically name, capacity, facilityId, ExchangeResourceMailbox ← MISLOCATED; belongs on data.Calendar
FacilitySpaceContact cms Public display — what the space looks like on the website nameOverride, description, amenities, photos, isReservable (discovery flag), bookingRules ← MISLOCATED; belongs on ReservationConfig
FacilitySpaceReservationConfig organization Design target Booking policy — how reservations of this space work FacilitySpaceId, RequiresApproval, RequiresLibraryCard, RequiresPayment, Cost, OperatingHours, MaxDurationMinutes, MaxLeadDays, BufferBefore/AfterMinutes, MaxConcurrentBookings, BlackoutDates, Notes
Calendar data Time view and Exchange broker for this space facilitySpaceId (FK), exchangeResourceMailbox (nullable), isPublic (bool). See Calendar & Time.

isReservable lives on FacilitySpaceContact as a display and discovery flag — "should a 'Reserve this space' button appear on the website?" This is distinct from whether the space has a ReservationConfig record (the operational flag). Both must be true for citizen-facing booking to work.

ExchangeResourceMailbox is opt-in per space and lives on data.Calendar (not on FacilitySpace or FacilitySpaceReservationConfig). The Calendar is the Exchange broker — modules push to the Calendar; the Calendar controller handles Exchange sync where exchangeResourceMailbox is set. See Calendar & Time.

WebPages / CMS Channel

The CMS module manages the public website's page tree — navigation, content blocks, embeds, and media. It is the primary channel for non-transactional, staff-authored content.

CMS pages do not store operational data directly. They reference it via Contact records (see above), web component embeds (see below), or explicit FKs in CMS page metadata. This allows staff to control the public presentation of a facility, program, or department without touching the operational record.

Open design question: CMS ↔ Org linkage

A CMS page about a Department does not currently have a verified FK to organization.Department. If a department is renamed or merged, the CMS page continues to show the old data unless a staff member manually updates it. The Contact pattern resolves this for structured entities (Facility, FacilitySpace, Program) but the question for free-form CMS pages remains open: should CMS pages carry an optional sourceType / sourceId pair to declare "this page is about Department 12"?

eNotifier Channel

eNotifier manages email distribution to opt-in subscriber lists. It is not a transactional email system — it is a bulk communication channel for keeping subscribers informed about topics they care about.

Interest Lists and Subscribers

Citizens subscribe to Interest Lists. An Interest List is a named topic (e.g. "Job Openings", "Parks Programs Updates", "City Council Agendas"). Staff manage Interest Lists in the admin portal; citizens manage their own subscriptions on the public website.

Trigger Model: Pull vs. Auto-Push

There are two models for how eNotifier campaigns are created and sent:

Pull (staff-composed) — default, always available.
A staff member opens eNotifier, selects an Interest List, composes a message (choosing content blocks from the system), and sends. The staff member controls timing, content, and which list receives it. This is the correct model for most communications — announcements, updates, newsletters. A rich multi-item content block editor (e.g. "add 3 programs to this email") eliminates the need to send one email per item.
Auto-push (Workflow Engine opt-in) — for transactional broadcast.
A specific Interest List can be configured with an auto-push rule in the Workflow Engine: when trigger condition X occurs, compose template Y and send to list Z. Example: "When a Job Posting is published, send a notification to the 'Job Openings' list." This is opt-in per list, not the default. It is appropriate only when the trigger is reliable, the audience is genuinely interested in every item of this type, and the volume is low enough that subscribers won't feel spammed.

Why pull-default matters

The historical misuse pattern was: staff sent one email per program session because there was no way to batch multiple programs into a single email. Auto-push on program publish would have replicated this at system level — one email per publish event. The solution is a content block editor that lets staff select multiple items for a single email, making the pull model genuinely usable for batch communications. Auto-push should only be used where a 1:1 trigger-to-email ratio is actually correct (e.g. a single job posting going to job-seekers who want to know about every opening).

EnotifierEmployee — staff list hygiene

EnotifierEmployee records link Interest Lists to staff members. When a campaign is sent to an Interest List, the linked employees also receive it. Currently these records are not linked to organization.Employee records — they are free-text email addresses stored separately. When a staff member leaves, their EnotifierEmployee rows are not automatically removed.

Audit finding (MEDIUM-HIGH priority): EnotifierEmployee should carry a FK to organization.Employee so that staff departures automatically clean up list subscriptions. Address this before the next significant staff turnover to prevent campaign leakage to inactive mailboxes.

Message assembly

Campaigns are composed from typed content blocks: announcements, program listings, event listings, news items, media items, and plain text. Each block type renders differently in the email template. The admin portal provides a multi-item block editor — staff should not be constrained to one item per block, and the block editor should make it trivially easy to add multiple items of the same type in one campaign.

Web Components Channel

MyGov exposes data through embeddable web components — custom HTML elements (<auburn-calendar>, <auburn-programs>, etc.) that can be dropped into any webpage and render live data from the MyGov API. Components are defined in static/components/.

Components serve two audiences:

  • Internal CMS pages — CMS page blocks can embed a component by name and configuration. This is how calendar views, program listings, and facility availability widgets appear on the public website without hand-coding them.
  • External pages — Third-party sites (e.g. a partner organization's website) can include the component script tag and embed live MyGov data with a single line of HTML.

Components communicate with the MyGov API via standard GET requests. They are read-only from the component side — they display data, they do not write it. Forms embedded in pages are a separate mechanism (see Forms).

Transactional Email

Transactional emails are single-recipient, system-triggered messages: a reservation confirmation sent to the person who made the reservation, a form submission receipt, a registration confirmation. These are distinct from eNotifier bulk campaigns.

Transactional emails are sent via the Workflow Engine's MailHandler or MSGraphHandler/SendTemplate action step. The Handlebars template renders the message body using the form response or record data as context. A staff member "notify attendees" action (e.g. from a Program detail page) is also transactional — one email per attendee, not a bulk campaign. See Workflow Engine for handler detail.

Email template management

Email templates are Handlebars files stored in the API codebase. They are not currently editable by staff in the admin portal — a code change is required to modify a transactional template. This is a known gap for operational flexibility. Whether templates should be admin-editable is an open design question.

Cross-Channel Design Questions

Should systems have a standard eNotifier integration point?

Currently, eNotifier integration is ad hoc — each module that wants to notify subscribers has to explicitly call the eNotifier API or configure Workflow Engine actions. Is there a standard "publishable" interface that any module can implement to opt into eNotifier distribution? This would reduce the per-module integration effort and make the Distribution layer a genuine platform feature rather than a series of one-off integrations.

Should CMS pages declare a sourceType / sourceId?

See the "CMS ↔ Org linkage" question above. The broader question is whether CMS pages should be first-class integration points — declaring what operational record they represent — or whether they remain fully editorial with Contact records as the only bridge.

Who owns the canonical public URL for a record?

A Facility has a CMS page, possibly a web component embed on another page, and maybe a direct API endpoint link. Which URL is the "official" URL for a facility? This matters for eNotifier deep links, CalendarEvent url fields, and web component link targets. Currently there is no canonical URL registry — this is resolved inconsistently across modules.

What is the distribution channel for Programs push?

When a new Parks program is published, who is notified and how? Currently: nothing automatic — staff must compose an eNotifier campaign. The auto-push model (Workflow Engine → eNotifier) would work for a "Parks Programs" interest list if the volume is right and subscribers genuinely want one email per program. A weekly digest approach (staff-composed, multi-program block) may be more appropriate. This is an open question that should be resolved with Parks staff before implementing any automation.

Audit Findings

FindingPriorityDetail
EnotifierEmployee not linked to org.Employee MEDIUM-HIGH Free-text email addresses. Staff departures do not clean up subscriptions. Add FK to organization.Employee; set ON DELETE CASCADE or soft-delete pattern.
bookingRules on FacilitySpaceContact MEDIUM Untyped JSON blob carrying booking policy on a display record. Migrate to FacilitySpace.reservationConfig JSON field (design target). Tracking in Reservations.
ExchangeResourceMailbox on FacilitySpace MEDIUM Field is correctly located on FacilitySpace (inventory association). No migration needed — previously noted as mislocated. See Location.
Email templates not admin-editable LOW Transactional templates require code changes. No timeline for admin template editor; accept current constraint until an explicit need drives the work.
No canonical public URL registry LOW CMS page URL for a Facility is not stored on the Facility record. Deep links in eNotifier and calendar entries are manually constructed.
Standard publishable interface — deferred design consideration DEFERRED Whether the platform should define a common interface for "publish to distribution channels" (rather than each module wiring its own eNotifier/Workflow actions) is a future architectural consideration. Does not block any current module build.
CMS page sourceType/sourceId FK — deferred design consideration DEFERRED Formally declaring which operational record a CMS page represents (via sourceType/sourceId) would enable automated deep-link resolution. Deferred until a concrete use case drives the work.