Skip to main content
OpenCouncil’s notification system automatically alerts citizens about council meeting subjects that match their interests, location preferences, and subscribed topics. Notifications are delivered via email, WhatsApp, and SMS with intelligent matching and approval workflows.

Notification channels

Email

Rich HTML emails via Resend with formatted content, links, and meeting details.

WhatsApp

Template messages via Bird API with quick action buttons and meeting summaries.

SMS

Automatic fallback to SMS when WhatsApp delivery fails, ensuring message delivery.

How notifications work

The notification system uses a sophisticated matching engine to connect users with relevant content:
1

Meeting processing

When a meeting is summarized, the system identifies subjects and their importance levels based on topic and proximity.
2

User matching

Each subject is matched against user preferences using four priority rules:
  • High importance subjects notify everyone
  • Normal importance subjects notify users interested in the topic
  • Near proximity subjects notify users within 400m
  • Wide proximity subjects notify users within 1.5km
3

Notification creation

Matched notifications are created in “pending” status, grouped by meeting and user, with delivery records for each channel.
4

Approval workflow

Depending on administrative body settings, notifications are either:
  • Auto-sent immediately after creation
  • Held for admin approval before sending
  • Disabled entirely
5

Multi-channel delivery

Approved notifications are sent with rate limiting (500ms delays) via:
  • Email with HTML formatting
  • WhatsApp templates with SMS fallback

Matching rules

The notification matching engine uses four rules with clear priorities:
Notify everyone regardless of preferencesWhen a subject is marked as high importance:
if (topicImportance === 'high') {
  matches.add({ subjectId: subject.id, reason: 'generalInterest' });
  continue;
}
Example: Budget approvals, major infrastructure decisions, emergency measures
Admins set importance levels per subject to control notification scope.
From src/lib/notifications/matching.ts:31-94

User preferences

Users configure notification preferences to control what they receive:
Users select topics they care about from a predefined list:
  • Transportation & Traffic
  • Environment & Parks
  • Urban Development
  • Education & Culture
  • Public Safety
  • Budget & Finance
  • Health & Social Services
Topics are matched against subject classifications from the AI summarization system.
Users add specific locations they want to track:
  • Home address - Get notified about nearby decisions
  • Work location - Stay informed about your commute area
  • Other places - Parks, schools, businesses you care about
Each location is stored as a geographic point with coordinate-based proximity matching.
Users choose how they want to receive notifications:
  • Email only - Traditional email notifications
  • Messages only - WhatsApp or SMS notifications
  • Both channels - Maximum coverage with email + messages
  • None - Pause all notifications
A separate delivery record is created for each enabled channel.

Approval workflows

Administrative bodies control notification behavior: From src/lib/tasks/summarize.ts:194-246

Delivery implementation

The notification delivery system handles multi-channel sending with graceful fallbacks:
async function sendEmailDelivery(delivery: NotificationDelivery) {
  const result = await sendEmail({
    from: 'OpenCouncil <[email protected]>',
    to: delivery.email,
    subject: delivery.title,
    html: delivery.body
  });
  
  if (result.success) {
    await updateDeliveryStatus(delivery.id, 'sent');
    return true;
  }
  
  await updateDeliveryStatus(delivery.id, 'failed');
  return false;
}
From src/lib/notifications/deliver.ts:29-174
Rate limiting is critical - Without the 500ms delay, you risk hitting API rate limits on Resend, Bird, or other delivery services.

Notification impact calculation

Before creating notifications, calculate the impact to estimate reach:
const impact = await calculateNotificationImpact(
  subjects,
  usersWithPreferences,
  subjectImportanceOverrides
);

console.log(`Will notify ${impact.totalUsers} unique users`);
console.log('Per-subject breakdown:', impact.subjectImpact);
// {
//   'subject-123': 45,  // 45 users will be notified
//   'subject-456': 12,  // 12 users will be notified
// }
From src/lib/notifications/matching.ts:100-145
Impact calculation is useful for admin dashboards to show how many people will be notified before approval.

Configuration

Set up notification services in your environment:
# Email via Resend
RESEND_API_KEY=re_your_resend_api_key

# WhatsApp & SMS via Bird
BIRD_API_KEY=your_bird_api_key
BIRD_WORKSPACE_ID=your_workspace_id
BIRD_CHANNEL_ID=your_channel_id

# Application URLs for notification links
NEXTAUTH_URL=https://opencouncil.gr

# Optional: Discord alerts for admins
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...

Welcome notifications

New users receive a welcome notification with platform information:
import { sendWelcomeNotification } from '@/lib/notifications/welcome';

await sendWelcomeNotification({
  userId: newUser.id,
  email: newUser.email,
  name: newUser.name,
  language: 'el' // Greek locale
});
Welcome emails include:
  • Platform introduction
  • How to set up preferences
  • Examples of notification types
  • Links to user profile and documentation
From src/lib/notifications/welcome.ts

Notification tokens

Unsubscribe and preference management uses secure tokens:
import { generateNotificationToken, verifyNotificationToken } from '@/lib/notifications/tokens';

// Generate token for unsubscribe link
const token = await generateNotificationToken({
  userId: user.id,
  action: 'unsubscribe',
  expiresIn: '30d'
});

const unsubscribeUrl = `${baseUrl}/notifications/unsubscribe?token=${token}`;

// Verify token when user clicks link
const payload = await verifyNotificationToken(token);
if (payload.action === 'unsubscribe') {
  await updateUserNotificationPreferences(payload.userId, { enabled: false });
}
From src/lib/notifications/tokens.ts
Tokens use HMAC-SHA256 signatures and expire after 30 days for security.

Notification content

Content generation creates localized, formatted messages:
import { generateNotificationContent } from '@/lib/notifications/content';

const content = await generateNotificationContent({
  notification: notificationRecord,
  user: userRecord,
  locale: 'el',
  channel: 'email' // or 'whatsapp', 'sms'
});

// Returns:
// {
//   title: 'Νέα συνεδρίαση: Δημοτικό Συμβούλιο',
//   body: '<html>...</html>',  // For email
//   // or plain text for SMS
// }
From src/lib/notifications/content.ts

API reference

matchUsersToSubjects
async function
Matches users to relevant subjects based on preferences
releaseNotifications
async function
Sends all pending notifications for given IDs

Next steps

AI summaries

Learn how summaries trigger notification creation

Search

Users can search topics they’re interested in

Transcription

Understand the data pipeline feeding notifications

User guide

Help users set up their notification preferences

Build docs developers (and LLMs) love