Learn how to plan, build, and launch a web app for internal announcements with read receipts, roles, targeting, and simple analytics.

An internal announcements web app solves a simple but costly problem: important updates get missed, and nobody can confidently answer, “Did everyone see this?” Email threads, chat channels, and intranet posts create noise, and accountability gets blurry—especially for policy changes, security notices, office closures, and benefits deadlines.
With read receipts built in, the outcome shifts from “we sent it” to “we can confirm it was read.” That clarity helps teams act faster, reduces repeat questions, and gives HR and managers a reliable way to follow up without guessing.
This isn’t just an HR tool. It’s an employee communications system used by different groups for different reasons:
The key is that every audience benefits: publishers know what happened, and employees know where to look so they don’t miss critical announcements.
Define the app’s purpose in one sentence: deliver key announcements to the right employees and confirm who read them.
That implies a few product decisions you’ll make later (targeting, role-based access control, audit trail), but keep the “why” crisp. If you can’t explain why a read receipt matters for your organization, you’ll struggle to decide what data to store and what reporting to build.
Pick metrics that reflect both delivery effectiveness and employee behavior:
Set targets by announcement type. A “free lunch Friday” post and a “new security requirement” post shouldn’t share the same goal. For critical messages, you might aim for 95% read within 24–48 hours, and use that target to shape notifications and follow-ups later.
If you want a north-star metric, use: % of critical announcements read by the full target audience within the required timeframe.
A clear scope prevents your announcements app from turning into a “do-everything” portal. Start by writing down who will use it (comms, HR, IT, managers, every employee) and what success looks like (e.g., critical updates acknowledged within 24 hours).
Define a first release that solves the core problem: publishing targeted announcements and confirming they were read.
Must-have features (v1):
Nice-to-have features (later):
If you want to validate scope quickly, a fast prototype can de-risk the hard parts (targeting, receipt logic, dashboards) before you invest in a full build. For example, teams often use Koder.ai to spin up an internal web app via chat—then iterate on the flows (feed, detail view, acknowledge) and export the source code once the requirements are stable.
Different announcements need different expectations. Agree on a small set of types up front:
For each type, capture required fields (expiry date, acknowledgment required, priority) and who is allowed to publish.
Be specific so engineering and stakeholders align:
This scope document becomes your build plan and your change-control reference when new requests arrive.
Clear roles and permissions keep announcements trustworthy, prevent accidental company-wide posts, and make read receipts defensible when questions arise later.
Admin manages the system: user provisioning, org settings, retention rules, and integrations. Admins don’t need to author announcements day to day.
Publisher creates and publishes announcements. This is typically Comms, HR, or IT.
Manager can draft or request announcements for their team and view receipts for announcements they own (or for their reporting line).
Employee reads announcements and can acknowledge them (if required). Employees generally shouldn’t see other people’s receipts.
Auditor (optional) has read-only access to published announcements, audit trail, and exports for compliance reviews.
At minimum, define permissions for: create, edit, publish, archive, view receipts, and export. Implement permissions at the action level (not just by role) so you can adapt later without rewriting logic.
A practical default:
If approvals matter, separate drafting from publishing:
Document these rules in a short “access policy” page and link it internally (e.g., /help/access-policy).
Before you sketch features, sketch moments: what an employee needs to do in under 10 seconds, and what an admin needs to do without training. A clear UX also reduces “I didn’t see it” disputes once you add read receipts.
Login should be frictionless: single button sign-in (if available), clear error states, and a direct path back to where the user left off.
Feed is the home base. Prioritize scannability: title, short preview, category/tag, targeting badge (optional), and status (Unread/Read/Acknowledgement required). Add a simple filter for Unread and a search bar.
Announcement detail is where receipts are earned. Show the full content, attachments/links, and an obvious read state. Automatic “read on open” is tempting, but consider accidental opens. If acknowledgements are required, separate “Read” from “Acknowledge” with clear wording.
Compose should feel like a lightweight editor: title, body, audience selector, publish timing, and preview. Keep advanced options collapsed.
Admin can start as a single page: manage users/roles, create groups, and view announcement performance.
Use readable typography, strong contrast, and visible focus outlines. Ensure all actions work by keyboard.
Design for quick mobile reads: large tap targets, a sticky “Acknowledge” button (when needed), and loading states that don’t block the content.
A clear data model makes read receipts reliable, targeting predictable, and reporting fast. You don’t need dozens of tables—just a few well-chosen entities and rules about how they relate.
At minimum, model these:
For Announcement, include:
Also consider metadata you’ll want later: created_by, updated_by, status (draft/scheduled/published), and timestamps. This supports auditing without extra tables.
Targeting is where many internal tools get messy. Pick a strategy early:
Explicit user list: store the exact set of user IDs for an announcement.
Best for small, precise audiences. Harder to manage for large orgs.
Group filters: store rules like “Team = Support” or “Location = Berlin.”
Best for recurring patterns, but audiences can change as people move teams.
Snapshots (recommended for receipts): store filters for authoring, then resolve them at publish time into a fixed list of recipients.
This keeps reporting and receipts stable: the people who were targeted at publish time stay the audience, even if someone changes teams later.
Receipts can grow quickly. Make them easy to query:
This prevents duplicates and makes common screens fast (e.g., “Did Alex read this?” or “How many reads does Announcement #42 have?”).
Read receipts sound simple (“did they read it?”), but the details determine whether your reporting is trusted. Start by defining what “read” means in your organization—then implement that definition consistently.
Pick one primary signal and stick to it:
Many teams track both read and acknowledged: “read” is passive, “acknowledged” is an intentional confirmation.
Create a dedicated receipt record per user per announcement. Typical fields:
user_idannouncement_idread_at (timestamp, nullable)acknowledged_at (timestamp, nullable)Optional diagnostics like device_type, app_version, or ip_hash should be added only if you truly need them and have policy approval.
To avoid double counting, enforce a unique constraint on (user_id, announcement_id) and treat receipt updates as upserts. This prevents inflated “read” numbers from repeated opens, refreshes, or notification clicks.
Announcements often get updated. Decide upfront whether edits should reset receipts:
A simple approach is to store an announcement_version (or content_hash) on the receipt. If the version changes and the change is marked “requires re-acknowledgement,” you can clear acknowledged_at (and optionally read_at) while keeping an audit trail of past versions.
Done well, receipts become a reliable measure—without turning into surveillance or noisy, inconsistent data.
A maintainable internal announcements web app is less about chasing the newest tools and more about picking well-supported pieces your team can run for years. Aim for a stack with strong documentation, a large talent pool, and straightforward hosting.
A proven baseline is a mainstream web framework paired with a relational database:
Relational databases make it easier to model announcements, audiences, and receipt records with clear relationships, constraints, and reporting-friendly queries.
If you prefer to move faster with a modern default stack, Koder.ai commonly generates React frontends with a Go backend and PostgreSQL—useful when you want a maintainable baseline without hand-wiring every CRUD screen and permission check from scratch.
Even if you’re building a server-rendered app, define clean REST endpoints so the UI and future integrations stay simple:
GET /announcements (list + filters)POST /announcements (create)POST /announcements/{id}/publish (publish workflow)POST /announcements/{id}/receipts (mark read)GET /announcements/{id}/receipts (reporting views)This keeps responsibilities clear and makes auditing easier later.
Real-time is nice, not required. If you need instant “new announcement” badges, consider:
Start with polling; upgrade only if users notice delays.
Avoid storing large files in your database. Prefer object storage (e.g., S3-compatible) and keep only metadata (filename, size, URL, permissions) in the database. If attachments are rare and small, you can begin with local storage and migrate later.
Authentication is the front door to your announcements app—get it right early so every later feature (targeting, receipts, analytics) inherits the same trust model.
For most workplaces, SSO is the default because it reduces password risk and matches how employees already sign in.
Pick one approach and be consistent across the app:
HttpOnly, Secure, and SameSite=Lax/Strict cookies. Rotate session IDs on login and privilege changes.Define both idle timeout and absolute session lifetime so shared devices don’t stay logged in indefinitely.
Authentication proves identity; authorization proves permission. Enforce authorization checks on:
Treat these checks as mandatory server-side rules—not UI hints.
Even internal apps need guardrails:
A good composer is less about fancy formatting and more about preventing mistakes. Treat every announcement like a mini-publishing process: clear ownership, predictable states, and a way to fix issues without editing history into a mess.
Use a simple, visible status model:
To keep accountability, store who moved it between states and when (an audit trail that’s easy to read later).
Scheduling avoids “send it now” pressure and supports global teams.
Make the UI explicit: show the current timezone, and warn if expire_at is earlier than publish_at.
Pick one content format and stick to it:
For most teams, basic Markdown (headings, bullets, links) is a practical middle ground.
If you support attachments, set expectations upfront:
If virus scanning is available in your storage provider, enable it; otherwise, at least restrict executable types and log uploads for follow-up.
Delivery is the bridge between “we published an announcement” and “employees actually saw it.” Aim for a few clear channels, consistent rules, and easy-to-understand preferences.
Start with an in-app experience: a “New” badge in the header, an unread count, and a feed that highlights unread items first. This keeps the system self-contained and avoids relying on inboxes.
Then add email notifications for users who don’t live in the app all day. Keep emails short: title, first line, and a single button that links to the announcement detail page.
Push notifications can be optional (and later), because they add complexity across devices. If you do add them, treat push as an extra channel—not the only one.
Give users control without making settings overwhelming:
A simple rule works well: default everyone to in-app + email for high-importance categories, and let users opt down (except for legally required notices).
Urgent posts should be visually distinct and can be pinned to the top until read. If policy requires it, add an “Acknowledge” button separate from a normal read receipt, so you can report on explicit confirmation.
Add guardrails: throttle bulk emails, require elevated permissions to send urgent notifications, and provide admin controls like “limit urgent posts per week” and “preview recipient count before sending.” This keeps the notification system trusted rather than ignored.
Read receipts only become useful when they answer practical questions: “Did this reach the right people?” and “Who still needs a nudge?” Keep reporting simple, fast to understand, and limited to what publishers actually need.
Start with a single dashboard view per announcement that shows three numbers:
If you store events, calculate these counts from your receipt table rather than mixing logic into the UI. Also include a small “last updated” timestamp so publishers trust what they’re seeing.
Add filters that reflect real operational slices, without turning the app into a BI tool:
When filters are applied, keep the same delivered/read/unread summary so it’s easy to compare segments.
CSV export is helpful for audits and follow-ups, but it should include the least data needed. A good default is:
Avoid exporting device details, IP addresses, or full user profiles unless you have a clear policy and approval.
Position receipts as a way to confirm critical messages (policy changes, safety notices, outages), not to track productivity. Consider showing managers aggregated stats by default and requiring elevated permission for user-level drill-down, with an audit trail of who accessed it.
Privacy and reliability determine whether people trust your announcements app. Read receipts are especially sensitive: they can easily feel like “tracking” if you collect more than you need or keep it forever.
Start with data minimization: store only what you need to prove a receipt happened. For many teams that’s a user ID, announcement ID, timestamp, and the client source (web/mobile)—not IP addresses, GPS data, or detailed device fingerprints.
Define retention policy options up front:
Document this in a short, plain-language privacy note inside the app (link it from /settings).
Maintain an audit trail for key actions: who published, edited, archived, or restored an announcement, and when. This helps resolve disputes (“Was this changed after it went out?”) and supports internal compliance.
Test the highest-risk paths:
Use separate environments (dev/staging/prod), run database migrations safely, and set up monitoring and backups. Track errors and job failures (notifications, receipt writes) so issues surface quickly.
If you’re using a platform approach, prioritize the operational features you’ll need in practice—repeatable deployments, environment separation, and rollback. (For example, Koder.ai supports deployment/hosting plus snapshots and rollback, which can reduce risk while you iterate on internal workflows.)
Common upgrades: multilingual announcements, reusable templates, and integrations (Slack/Teams, email, HR directory sync).
A read receipt answers the operational question: who actually saw (and possibly acknowledged) a critical message. It reduces follow-up guesswork for things like policy changes, security notices, office closures, and benefits deadlines, and it turns “we sent it” into “we can confirm it was read.”
Good v1 metrics are:
read_at (or acknowledged_at).Set different targets by announcement type (e.g., urgent/security vs. culture/news).
A solid v1 scope typically includes:
Keep “nice-to-haves” (approvals, templates, reactions, advanced analytics) for later unless you truly need them immediately.
Start with clear roles and explicit permissions:
Pick one primary definition and apply it consistently:
Many teams track both: read_at for passive reads and acknowledged_at for required confirmations.
Use a dedicated receipts table with one row per user per announcement:
user_id, announcement_idread_at (nullable)acknowledged_at (nullable)Decide upfront how edits affect receipts:
A practical pattern is storing an announcement_version (or content_hash) and clearing acknowledged_at only when the publisher marks the change as “requires re-acknowledgement,” while keeping an audit trail of what changed and when.
Targeting options generally fall into:
Snapshotting keeps receipts and reporting stable: the audience is “who was targeted at publish time,” not “who matches the filter today.”
Use SSO (SAML/OIDC) if you can; it reduces password risk and aligns with existing identity management. Regardless of auth method:
Treat authorization as a required backend rule, not a UI setting.
Keep receipts useful without becoming surveillance:
Include a short, plain-language privacy note inside the app (e.g., linked from ).
Define permissions by action (create/edit/publish/archive/view receipts/export), not just by role name.
Enforce a unique constraint/index on (announcement_id, user_id) and write receipts as upserts to avoid duplicates from refreshes or multiple devices.
/settings