Repository navigation
Eagle Notify
How EPIC sends project update emails. The service is eagle-notify. Citizens subscribe to project updates by email and manage their subscriptions from links in those emails. Staff sign in with IDIR through Keycloak to manage email templates and campaigns.
Local setup, publishing steps and the status of each feature are in the repo: README and docs/STATUS.md. This page covers how the service fits into EPIC.
flowchart LR
Citizen["Citizen"] -->|"email links"| Pages["Storage static site<br/>React app"]
Citizen -->|"subscribe form on project pages"| API
Pages --> API["notify-api-<env><br/>Azure Functions"]
Staff["Staff (IDIR)"] --> Pages
Staff -.->|"sign in"| KC["Keycloak<br/>realm eao-epic"]
DEMI["demi-api"] -->|"POST /api/events"| API
API --> Cosmos[("Cosmos DB<br/>NoSQL")]
API --> ACS["Azure Communication<br/>Services Email"]
ACS -->|"email"| Citizen
ACS -.->|"delivery reports<br/>(Event Grid)"| API
The service has two parts on two hostnames: a static React site and a Function App. No Front Door
route puts both on one origin, so the API keeps a CORS allow-list of the frontends that call it
(frontendHostNames in azure/main.test.bicepparam).
| Part | Where | What it is |
|---|---|---|
| API | api/ |
Azure Functions v4, Node 22. HTTP routes for the subscriber flow, event ingest, health and staff screens. An Event Grid trigger handles delivery reports. Two timers send mail. |
| Web | web/ |
React 19 and Vite 8, built with Node 24. Public subscribe and manage screens plus the staff screens. Its own package, not part of the root Yarn workspace. |
| Infrastructure | azure/ |
Bicep: Cosmos account, Function App, static site, identity. Email is a separate deployment, azure/email.bicep. |
| CI and deploy | .github/workflows/ |
pr.yaml and deploy-staging.yaml. |
| Decisions | docs/adr/ |
Architecture decision records. |
Subscribers never sign in. A subscriber proves who they are by holding a code that was emailed to them. The code is in the link, so confirm, manage, unsubscribe and undo all work from the email. Confirming a subscription records consent. Unconfirmed and deleted rows expire through Cosmos TTL, not a scheduled job.
Staff sign in with Keycloak. The React app starts the IDIR sign-in only on the staff routes. The API
checks every staff request itself (api/src/auth.js): it verifies the token signature against the
realm keys, then checks the issuer, the client the token was issued to, and a staff role. See
Keycloak Configuration for the realm.
Mail goes out in two ways.
- Campaigns: staff write an email template and start a campaign. A timer (
campaignSlice) runs every 5 minutes, except during the 14:00 UTC hour, and sends the campaign in slices. - Daily digest: a timer (
dailyDigest) runs at 14:00 UTC. It sends each subscriber one message that covers the previous UTC day's events for every service they follow.
Events come from DEMI. When an update is published, demi-api posts it to POST /api/events with a
function key. The payload and retry rules are in Eagle DEMI.
Staff edit emails in a block editor in the browser. The server stores MJML and compiles it to HTML, with Handlebars filling in the variables. The plain-text part is derived from that HTML. Nothing the browser produces is sent as is.
Azure Communication Services reports delivery results through Event Grid. The API records bounces against the subscriber.
- React 19 and Vite 8, with react-router 7 in hash-router mode. The hash router is deliberate: the
static site has no rewrite rule, and the subscriber code in an email link sits after the
#, so it never reaches a server log. - Staff screens are lazy-loaded, so a citizen who opens an unsubscribe link downloads no staff code.
- TanStack Query holds staff data.
- Styling vendors the eagle-design-kit CSS. The header and footer follow the design shared with eagle-public, with Notify's own name in the header. Screens change with no animation.
| Environment | State |
|---|---|
| test (staging) | API at notify-api-test.azurewebsites.net. Pages on the storage account's static site. Cosmos account notify-cosmos-test. Email through an Azure Communication Services resource. |
| prod | None. |
Staging is spelled test in Azure and in the GitHub environment name.
Run the Deploy to staging workflow (deploy-staging.yaml) from the Actions tab of the repo. It has a
manual trigger and uses the GitHub environment test. The job:
- Builds the web app and the API package.
- Logs in to Azure with OIDC, using the CI identity created by
scripts/bootstrap-cicd.sh. - Deploys the API zip and waits for the deployment to succeed.
- Checks that
/api/healthreturns 200. - Saves a copy of the
$webcontainer as theweb-rollback-<run id>artifact (kept 30 days). - Uploads the pages,
index.htmllast, then checks the site serves the new entry file.
To roll back the pages, put the old index.html back from the saved copy. Infrastructure is deployed
by hand with Bicep; the steps are in the README under Publishing. Every PR runs lint, tests, the Bicep
compile and a contrast check (pr.yaml).
| Question | Look at |
|---|---|
| What is built, what is blocked | docs/STATUS.md |
| Why Functions and Cosmos, and why not listmonk | docs/adr/0001-runtime-and-datastore.md |
| Subscriber routes | api/functions/subscriptions.js |
| Staff routes | api/functions/staff.js |
| Event ingest and daily digest | api/functions/events.js |
| Staff token checks | api/src/auth.js |
| Bicep |
azure/main.bicep, azure/modules/
|
| Web routes | web/src/router.tsx |
- Architecture Overview
- Eagle DEMI - the event push contract
- Keycloak Configuration
Eagle Documentation
Operations
- Deployment Pipeline
- Helm Charts
- Reverse Proxy
- Rollback Procedures
- Eagle Public Release Lines
- Eagle Search Archive
- Azure Migration Strategy
- Azure Hosting
- Front Door Per-App Split
- Front Door Ban Ladder
- Error Logging
- Secrets Management
Architecture
- Architecture Overview
- API Architecture
- Analytics Architecture
- Angular Patterns
- Eagle Public Page Layout
- Eagle Public Adding A Page
- Extended Project Pages
- ENGAGE–EAGLE Integration
- BCGW Project Export
- Eagle Search
- Document Content Search
- Eagle DEMI
- Eagle DEMI Push Sweep
- Eagle DEMI Re-Push and Backfill
- Eagle Notify
- Project Data Models
- Async Jobs API
- Bulk Document Download
Configuration
Help
Repositories
Environments