Six incremental improvements: flash confirmation, form hint, recipient preview, read receipts, branded email, file attachments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6.4 KiB
Messaging System Enhancements — Design Spec
Date: 2026-03-11
Status: Approved
Scope: 6 incremental improvements to /wiadomosci messaging system
Context
NordaBiz messaging system has solid foundations: PrivateMessage model with threading (parent_id), UserNotification creation on send, email notifications via Microsoft Graph API, user blocking, and bell-icon notification dropdown. Missing: post-send confirmation, recipient preview, read receipts visibility, branded email template, file attachments.
Feature 1: Post-Send Flash Message
Goal: Inform sender that message was delivered and recipient will be notified.
Implementation:
- In
messages_send()route (blueprints/messages/routes.py), addflash()after successful send - Message text:
"Wiadomość wysłana! Odbiorca zostanie powiadomiony emailem."(category:success) - If recipient has
notify_email_messages=False:"Wiadomość wysłana!"(without email mention) - Redirect to sent box remains unchanged
Files: blueprints/messages/routes.py
Feature 2: Contextual Hint on Compose Form
Goal: Before sending, inform user what happens after they click "Wyślij".
Implementation:
- Add hint text below the submit button in
compose.html - Text:
"📧 Odbiorca zostanie powiadomiony o nowej wiadomości emailem" - Styled as muted/secondary text, small font, consistent with form design
- Static text — no backend changes needed
Files: templates/messages/compose.html
Feature 3: Recipient Profile Preview
Goal: After selecting recipient in autocomplete, show mini business card.
Implementation:
- Extend the
usersJSON injected intocompose.htmlwith:company_name,company_slug,position(job title) - Route
messages_new()already queries all active verified users — add LEFT JOIN toUserCompanyPermissions+Companyto get company info - In compose.html JS, when recipient is selected via
selectRecipient(), populate a preview card below the selected-recipient chip:- Avatar initials (already exist in autocomplete items)
- Company name (linked to company profile if available)
- Position/role text
- Preview card hidden when no recipient selected, shown with fade-in on selection
Files: blueprints/messages/routes.py (extend user query), templates/messages/compose.html (preview card UI + JS)
Feature 4: Read Receipts in Sent Box
Goal: Sender can see which sent messages have been read.
Implementation:
- In
messages_sent()route, the query already returnsPrivateMessageobjects withis_readandread_atfields - In
sent.htmltemplate, add visual indicator per message:- Unread: single gray checkmark icon or "Wysłana" badge
- Read: double blue checkmark or "Przeczytana" badge with
read_attimestamp on hover (title attribute)
- CSS-only styling, no JS needed
- No new DB queries — data already available on the model
Files: templates/messages/sent.html
Feature 5: Branded Email Notification
Goal: Message notification email should use the same branded template as password reset and welcome emails.
Implementation:
- Current inline HTML in
messages_send()(lines ~203-212) replaced with call to a new helper function - New function
_build_message_notification_email(sender_name, subject, content_preview, message_url)inemail_service.py - Uses existing
_email_v3_wrap()template for consistent branding - Content: sender name, subject, first 200 chars of message body, "Przeczytaj wiadomość" CTA button
- Preserves existing logic: only sends if
recipient.notify_email_messages != False
Files: email_service.py (new template function), blueprints/messages/routes.py (use new function instead of inline HTML)
Feature 6: File Attachments
Goal: Allow attaching files (images + business documents) to messages.
Database Model
New model MessageAttachment:
class MessageAttachment(Base):
__tablename__ = 'message_attachments'
id = Column(Integer, primary_key=True)
message_id = Column(Integer, ForeignKey('private_messages.id', ondelete='CASCADE'), nullable=False)
filename = Column(String(255), nullable=False) # original filename
stored_filename = Column(String(255), nullable=False) # UUID-based on disk
file_size = Column(Integer, nullable=False) # bytes
mime_type = Column(String(100), nullable=False)
created_at = Column(DateTime, default=datetime.now)
Relationship on PrivateMessage: attachments = relationship('MessageAttachment', backref='message', cascade='all, delete-orphan')
File Storage
- Path:
static/uploads/messages/{year}/{month:02d}/{uuid}.{ext} - Reuse
FileUploadServicepattern but extend allowed types:- Images: JPG, PNG, GIF (max 5MB each)
- Documents: PDF, DOCX, XLSX (max 10MB each)
- Magic bytes validation for all types
- Max 3 files per message, max 15MB total
- No EXIF stripping for documents (only images)
Upload Flow
compose.html: file input with drag & drop zone (reuse forum pattern)- Files uploaded with the message form submission (multipart/form-data) — no pre-upload AJAX
messages_send()processes files after message creationmessages_reply()also supports attachments
Display
view.html: attachments listed below message content- Images: inline thumbnail preview (max 300px wide) with click to full size
- Documents: file icon + filename + size, click to download
sent.html/inbox.html: paperclip icon indicator if message has attachments
Migration
SQL migration: database/migrations/XXX_message_attachments.sql
- CREATE TABLE + indexes + GRANT permissions to
nordabiz_app
Files: database.py, blueprints/messages/routes.py, templates/messages/compose.html, templates/messages/view.html, templates/messages/sent.html, templates/messages/inbox.html, database/migrations/XXX_message_attachments.sql
Implementation Order
- Flash message (smallest, immediate UX win)
- Contextual hint (CSS only, no backend)
- Read receipts in sent box (template change, data exists)
- Recipient profile preview (query extension + JS)
- Branded email template (email_service refactor)
- File attachments (largest — new model, migration, upload logic, UI)
Each feature is an independent commit, deployable and testable separately.
Out of Scope
- Real-time WebSocket notifications (current 60s polling is sufficient)
- Message search
- Group messages / broadcast
- Message deletion by sender
- Typing indicators