AryonForge
v0.1.0 · Pre-release

Smart Announcement Banner for Confluence

Confluence   v0.1.0 · Pre-release

Targeted, scheduled announcements for Confluence with per-reader acknowledgement tracking — and a real page banner above page content.

Confluence · Forge app No external egress No sale of customer data Global settings
01

Overview

Announcements are global (site) or space, and the level is derived server-side from request.context.extension.space — never from the client. The space settings console only renders inside a space’s settings, which only that space’s administrators can reach, so a space lead can publish to their space without a site admin in the loop.

confluence:pageBanner is the only true banner surface across the three sibling apps, which is why the announcement card here has a banner density the Jira copy does not. The banner has a single slot, so the bootstrap hands it only the winning announcement — pinned first, then priority, then most recently updated, then the more specific scope.

Smart Announcement Banner is one of three sibling apps — Jira, Confluence and Bitbucket. They are separate apps because Forge app storage is scoped to the installation: installing one app on both Jira and Confluence creates two independent installations with completely separate storage, so a single app could never deliver “publish this announcement to Jira and Confluence”. Splitting makes that platform reality the design instead of a footnote. Bitbucket was already separate for a different reason — a single Forge app cannot mix Bitbucket workspace scopes with Jira/Confluence site scopes.

02

Highlights

Five announcement types, with a preview that cannot lie

Critical, Warning, Information, Success and Tip, each with its own colour, icon and lozenge; priority 1–5 plus a pin flag; an optional call-to-action button. The live preview renders the real reader component fed real normalized data, so what an admin previews is what ships.

Scheduling in the admin’s timezone, stored as UTC

Optional start and end instants, with a derived Live / Scheduled / Expired / Paused status computed against a server timestamp so every surface agrees.

Three dismissal modes

Require acknowledgement, dismissible with an optional snooze that brings it back, or always visible.

Delegated administration that gains no extra reach

A delegated console lists, reports on and can mutate only its own announcements. Readers see the levels merged, each tagged with its origin.

Insights

Acknowledgement and dismissal counts, distinct readers reached, breakdown by type, most-engaged announcements, and a recent reader-activity feed. “Reset reads” re-shows an announcement to everyone who already cleared it.

Bounded, conflict-safe storage

Announcements, target generations, current reader engagement, 90-day activity and the personal-account registry are separate entities. Conditional revisions prevent concurrent-admin clobbering; engagement epochs make “Reset reads” immediate without losing permanent acknowledgements to an event cap.

03

Modules and surfaces

confluence:globalSettings → the site admin console.

confluence:spaceSettings → the space admin console, inside a space’s own settings.

confluence:pageBanner → the banner strip above page content, merging site and space announcements into one slot.

confluence:globalPage → the announcement centre under Apps, site-wide only.

confluence:spacePage → the announcement centre inside a space, merging both levels.

confluence:homepageFeed → an announcements panel on Confluence Home, site-wide only.

On the banner, priority leads and scope only breaks ties. A site-wide incident marked Highest takes the slot from a space’s routine tip rather than losing it for being less specific.

04

User guide

Who this is forWhoever publishes announcements. Each console is already permission-gated by the product, so a delegated admin needs no extra grant — and gains no reach beyond their own container.

Every console manages only its own scope, and that is the point: a project or space lead can publish to their own people without a site admin in the loop. Each console states its reach under the heading, next to a scope lozenge.

SurfaceWhere to find it
Site consoleConfluence administration → Settings → Apps → Smart Announcement Banner
Space consoleSpace settings → Apps → Smart Announcement Banner
Reader surfacesThe banner above page content, the Announcements page inside a space, the Announcements panel on Home, and Apps → Announcements
  1. Open the console for the level you want to publish at

    You cannot choose the level in the composer — it comes from the page you are on. That is what stops a delegated admin publishing site-wide, and it is why the composer states the reach rather than offering it as a dropdown.

    Site reaches everyone in every space — use it for outages and org-wide policy. Space reaches people reading pages in that space — space restructures, content freezes.

  2. Write the content

    Click New announcement. Title (required, 120 characters) and Message (required, 2000 characters) are the two required fields; each new line in the message becomes its own paragraph for readers.

    Pick a Type — Critical, Warning, Information, Success or Tip — by what the reader must do, not by how loud you want to be. Critical on a routine notice trains people to ignore Critical. Priority runs 1–5 and affects ordering.

    The composer shows a live preview at the top that is the real reader component fed real validated data — what you see there is exactly what readers get.

  3. Decide how readers clear it

    Require acknowledgement — the reader clicks “Got it”. Recorded per person, reported in Insights, never returns. Dismissible — the reader closes it, and it returns after the snooze you set (0 hours makes the dismissal permanent for that reader). Always visible — no close control at all.

    Use Always visible sparingly: it cannot be cleared by the reader. Pin above other announcements puts this one first regardless of priority.

  4. Choose who sees it

    Everyone, only these people, or everyone except these people — matched on Atlassian account ids, comma- or newline-separated. Confluence also offers only these groups and everyone except these groups, matched on a stable group ID (a UUID), never a name — a group can be renamed, its ID cannot, so a rename never silently empties an audience. Find it under Settings → User management → Groups (the ID is in the URL), or from GET /wiki/rest/api/group/by-name?name=<name>. An optional Display names field holds labels for your own benefit and is not used for matching.

    “Everyone” means everyone this announcement reaches: on a delegated console that is the people in that container, not the whole site. Targeting narrows within the scope; it never widens it.

    An announcement targeted at anyone other than “Everyone” is hidden from unauthenticated readers, including exclude-modes — the app cannot confirm an anonymous reader is not one of the excluded people.

  5. Set the schedule, and read the line under the fields

    Starts and Ends are optional, entered in your timezone and stored as UTC. Leave both empty to run until you pause it.

    Use the calendar button rather than typing. The date field parses typed text in your browser’s locale, so 2026-08-20 is not read as 20 August everywhere. The line under the fields always spells out the window the pickers actually resolved to — read it before saving, because a start date in the past still counts as Live and the mistake is otherwise invisible until the announcement goes out early.

  6. Publish, or stage it paused

    The footer toggle reads Publish when saved / Save as paused. Saving as paused is always allowed, and is how you stage the next announcement while the current one is still running.

  7. Watch Insights, and reset reads if the content changes

    Acknowledgements (deduplicated per person), people engaged (distinct readers who acknowledged, dismissed or snoozed), and per-announcement breakdowns. A dismissible announcement produces no acknowledgements, so the table reports whichever number that mode actually generates rather than a permanently-zero column.

    Reset reads starts a new engagement epoch without changing the content, re-showing the announcement to everyone who already cleared it. Use it when an announcement’s content changed materially.

Good to know

  • Each console holds three announcements and shows one at a time. The three-stored limit counts everything the console can see — live, scheduled, paused and expired alike. Both limits are per console, not per site: the site console gets three, and so does every delegated console, so a lead filling their own budget can never stop another team publishing.
  • “The same period” means the whole window, not just right now. Two announcements queued for the same week clash even though neither is live yet — otherwise the rule would be broken later by the calendar rather than by anything you did. An announcement with no end date covers everything after its start, so scheduling a successor means giving the current one an end date first.
  • Two announcements cannot run at once in the same console, even with different audiences. If you need a notice for engineering and a different one for sales simultaneously, publish them from two different consoles. Readers see the levels merged.
  • Managing what exists: Edit reopens the composer and keeps read receipts; Pause/Resume hides from readers without deleting, also keeping receipts; Duplicate creates a paused copy titled “… (copy)” in the same scope; Delete removes the announcement and its read receipts, and cannot be undone.
  • Derived status: Live (active and inside its window), Scheduled (active, start not yet reached), Expired (active, past its end date), Paused (switched off — this beats the schedule, so a paused announcement never shows as expired).
  • Call-to-action links need both halves — a label with no destination renders a dead button. Only http(s) links and in-product paths starting with / are accepted; javascript:, data: and protocol-relative //host URLs are rejected in the composer and again on the backend.
  • What readers see: the banner above page content merges site-wide and that space’s announcements, and so does the Announcements page inside a space. The Announcements panel on Home and Apps → Announcements are not inside a space, so they show site-wide announcements only and say so.
  • Retention: acknowledgement and dismissal state is kept per reader for the lifetime of the announcement, so a busy announcement does not make older acknowledgements reappear. The separate recent-activity feed shows the 25 newest events and keeps activity for 90 days.
  • The page banner has a single slot, so only the winning announcement appears there — pinned first, then priority, then most recently updated, then the more specific scope. The other surfaces show the full list.
  • Ordering when levels mix: priority decides and scope only breaks ties. A site-wide incident marked Highest takes the banner slot from a space’s routine tip rather than losing it for being less specific.
  • The banner runs the message onto one line deliberately: it sits above page content, and a multi-paragraph strip pushes the page down — in the editor it takes writing space. Paragraphs are preserved everywhere a reader sees the full announcement, and the full text is always one click away.
  • Each reader’s badge names the space (e.g. Space ENG), because a feed can carry notices from a space and from the site at once, and two spaces may well have announcements with the same title.

If something looks wrong

The console refuses to publish a new announcement.

Either the console already holds three, or another announcement in it covers the same period. Both limits count paused and expired announcements too — delete something, or give the current announcement an end date before scheduling its successor.

An announcement went out earlier than expected.

A start date in the past still counts as Live. The line under the schedule fields spells out the window the pickers resolved to; typed dates are parsed in your browser’s locale, so use the calendar button.

A targeted announcement reaches nobody.

Audience matching fails closed. An unauthenticated reader matches no targeted announcement, and a failed group-membership lookup hides rather than shows — in both include- and exclude-group modes.

An announcement exists but does not appear in the page banner.

The banner has one slot. Another announcement won it — pinned beats priority, priority beats recency, and recency beats scope specificity. The space’s Announcements page shows the full list.

What it does not do

  • Publish across products. Each app has its own storage and cannot see the others’ announcements.
  • Let a delegated console reach beyond its own container, or let a client choose its own scope — the level is derived server-side from the product’s context.
  • Run two announcements at once in the same console, whatever their audiences.
  • Recover a deleted announcement or its read receipts.
05

Permissions & scopes

ScopeWhy it's needed
storage:appAnnouncements and engagement records
read:confluence-userResolving the current viewer’s group memberships for the include-groups / exclude-groups audience modes
read:confluence-groupsThe same group-membership lookup, via /wiki/rest/api/user/memberof
report:personal-dataScheduled privacy reporting of stored account IDs, and closed-account erasure
06

External access

No external block at all. Call-to-action links are rendered by UI Kit’s native link button in the product page itself, not fetched or embedded by the app, so they need no egress declaration.
07

Data stored

RecordKeyContentsRetention
Announcementscustom entityType, title, message, priority, pin flag, CTA, schedule, dismissal mode, scope and attributionUntil deleted
Audience targetscustom entity, one row per account, swapped by generationThe account IDs an announcement includes or excludesReplaced wholesale on each edit
Engagementcustom entity, with epochsWhich reader acknowledged or dismissed which announcement, and whenPermanent for acknowledgements; “Reset reads” advances an epoch rather than deleting
Activitycustom entity, idempotent receiptsThe recent reader-activity feed90 days
Personal account registrycustom entityThe account IDs the app holds, for privacy reportingReported on a daily scheduled trigger; erased on account closure
08

Security notes

Every resolver is gated on the module the platform rendered

  • That value comes from request.context, not the caller’s payload, and each admin module is already permission-gated by its product — so matching it is equivalent to requiring admin without an extra REST call per mutation.
  • Jira and Confluence both supply context.moduleKey, which is what the gate matches.
  • All gates fail closed on a missing value, and the mapping is one-to-one. Clients do not send a surface: admin modules cannot call reader engagement, reader modules cannot request admin analytics, and a delegated admin cannot claim global scope.

Scope is derived, then re-used as an authorization check

  • The write scope comes from request.context.extension and throws rather than widening if the container id is missing.
  • Every mutation then asserts the target announcement belongs to that exact scope. Two layers: the module gate proves which admin page you are on, the scope check proves which announcements it owns.
  • Matching is on the immutable container id; the human-readable key is carried for the reader badge only.

Fail-closed audience matching

  • An unauthenticated reader matches no targeted announcement.
  • A failed group lookup hides rather than shows — in both include-group and exclude-group modes.
  • Targeting narrows within an announcement’s scope; it never widens it.

Call-to-action links are parsed and origin-checked

  • An admin-supplied href is the one field that reaches every other reader’s browser, so only http(s) and in-product paths are stored.
  • javascript:, data:, protocol-relative URLs, backslashes and control characters are dropped.
  • Embedded credentials are rejected too, because https://trusted.example@evil.example/ reads as a link to the trusted host but navigates to the attacker’s.
  • Enforced on the backend and mirrored in the composer. An external CTA opens in a new tab; an in-product path stays in the current one.

Versioned, fail-closed migration

  • Legacy blobs are copied with stable ids, explicit predecessor truth tables, checkpoints and verification.
  • Ambiguous rows are paused and quarantined for admin repair. They never default to active, global or everyone.
  • The source blobs remain rollback input for one release.

Reader cost is bounded

  • The reader bootstrap filters before it reads engagement, so a site with many paused or out-of-window announcements does not pay a storage read per announcement on every page load.
09

Known limitations

  • The page banner has one slot. Only the winning announcement appears there; the rest are in the announcement centre.
  • This app starts with empty storage. It was registered fresh when the combined Jira + Confluence app was split in two — the Jira half kept the original id.
  • A cross-app fix is three separate reviews, not one patch applied thrice. The Jira and Confluence apps share roughly 85% of their logic as independent copies, and they have drifted.
10

Release notes

Version history for Smart Announcement Banner for Confluence. The most recent release is listed first.

  1. v0.1.0 Current

    First build as a standalone Confluence app.

    Split from the combined app

    • Registered fresh when the combined Jira + Confluence app was split in two, so this app starts with its own empty storage.
    • The split exists because Forge app storage is scoped to the installation: two installations of one app cannot read each other’s data, so a shared admin console could never honour “publish to both products”.

    What it does

    • A real page banner above page content — the only true banner surface across the three sibling apps — plus an announcement centre under Apps, a space announcement centre, and a Confluence Home panel.
    • Five announcement types, priority and pinning, an optional call-to-action, and a live preview rendering the real reader component.
    • Scheduling entered in the admin’s timezone and stored as UTC, with a server-derived status.
    • Targeting by everyone, specific accounts, or Atlassian groups — include or exclude.
    • Site and space scope levels, with delegated space administration.
    • Insights: acknowledgements, dismissals, distinct readers, breakdown by type, and a recent activity feed.

    Fixed during development

    • Free-text fields were losing all but the last character typed, because a controlled UI Kit text input re-renders with the previous render’s value between keystrokes. Fixed by making them uncontrolled.
    • Form state was being overwritten with stale text when moving between fields. Fixed by merging functionally rather than spreading the form prop.
11

Get it & contact

Marketplace link coming soon Contact support

Vendor

AryonForge

App ID

ari:cloud:ecosystem::app/482ac684-0eb7-4464-8aec-3a22cf19206b