Email Client Desktop App using egui
Email Client Desktop App using egui
Overview
Create a desktop email client application using Rust and the egui GUI framework. This app will display email data from a SQLite3 database (generated separately in #173).
Depends on: #173 (Fake Data Generator)
Project Structure
This should be implemented as a separate binary crate in the Cargo workspace:
Binary name: dwata-app-egui
Workspace structure:
dwata/
├── Cargo.toml # Workspace manifest
├── dwata-db/ # Shared library (from #173)
├── fake-data-generator/ # Binary from #173
└── dwata-app-egui/ # THIS BINARY (Issue #174)
├── Cargo.toml
└── src/
├── main.rs
├── ui/
└── worker/
Cargo.toml for dwata-app-egui:
[package]
name = "dwata-app-egui"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "dwata-app-egui"
path = "src/main.rs"
[dependencies]
dwata-db = { path = "../dwata-db" } # Shared database library
eframe = "0.27"
egui = "0.27"
dirs = "5.0"
Usage:
# Build and run the egui email client
cargo run --bin dwata-app-egui
# Or build in release mode
cargo build --release --bin dwata-app-egui
./target/release/dwata-app-egui
Using the Shared dwata-db Library
IMPORTANT: This application should use the shared dwata-db library crate for ALL database operations. Do NOT create your own db/ module or duplicate any database code.
What dwata-db Provides
The dwata-db library includes:
-
Database Models - All Rust structs:
use dwata_db::{Account, Email, Contact, Folder, Label, Attachment}; -
EmailRepository - All database query functions:
use dwata_db::EmailRepository; let repo = EmailRepository::new(db_path)?; // Available methods: repo.get_accounts()?; repo.get_emails_by_folder(folder_id)?; repo.get_email_by_id(email_id)?; repo.search_emails_by_subject(query)?; repo.get_contacts(account_id)?; repo.get_labels(account_id)?; repo.get_folders(account_id)?; repo.get_attachments(email_id)?; repo.get_all_attachments()?; repo.mark_as_read(email_id)?; repo.toggle_star(email_id)?; -
Schema Creation (if needed):
use dwata_db::create_tables;
Benefits
- No code duplication - Database logic defined once
- Type safety - Shared types across all binaries
- Consistent behavior - Same queries used by all apps
- Easy maintenance - Changes in one place
Code Structure (Simplified)
Since you're using the shared library, your code structure should be:
dwata-app-egui/src/
├── main.rs # Entry point, egui setup, DB path check
├── ui/
│ ├── mod.rs
│ ├── app.rs # Main app state and egui App trait
│ ├── navigation.rs # Column 1: Navigation panel
│ ├── email_list.rs # Column 2: Email list view
│ ├── email_detail.rs # Column 3: Email detail view
│ ├── contacts.rs # Contact list and detail views
│ ├── files.rs # Files browser view
│ ├── search.rs # Search bar and functionality
│ └── virtual_list.rs # Reusable virtual scrolling component
└── worker/
├── mod.rs # Worker thread logic
└── messages.rs # Request/Response types
Note: No db/ module needed - use dwata_db directly!
Database Connection
Database Location
The app will read from the SQLite database at:
- Linux/macOS:
~/.config/dwata/db.sqlite3 - Windows:
%APPDATA%\dwata\db.sqlite3
Example path: /home/username/.config/dwata/db.sqlite3
Database Requirements
- The app does NOT create the database - it must already exist (created by the generator from #173)
- On startup: Check if database file exists at expected location
- If database not found: Display error message and exit gracefully
- Error message should show expected database path
- Suggest running the fake data generator first
Example Implementation
use dwata_db::EmailRepository;
use std::path::PathBuf;
fn get_db_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
let config_dir = dirs::config_dir()
.ok_or("Could not determine config directory")?;
let db_path = config_dir.join("dwata").join("db.sqlite3");
if !db_path.exists() {
return Err(format!(
"Database not found at: {}\nPlease run the fake data generator first.",
db_path.display()
).into());
}
Ok(db_path)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let db_path = get_db_path()?;
let repository = EmailRepository::new(db_path)?;
// Use repository in your app
// ...
Ok(())
}
Architecture
Threading Model
The application must use separate threads for UI and data processing to maintain responsiveness:
- UI Thread: Handle rendering and user interaction with egui
- Worker Thread(s): Database queries, search operations, data processing
- Communication: Use message passing (`std::sync::mpsc` channels) between threads
Threading Flow
- User action in UI → UI sends request message to worker
- Worker receives request → queries database → sends result back to UI
- UI receives result → updates state → triggers re-render
Example: ``` User clicks "Inbox" folder → UI sends: FetchEmails { folder_id: 5 } → Worker queries DB → Worker sends: EmailsResult(Vec<Email>) → UI updates list and renders ```
User Interface Layout
3-Column Layout
Column 1: Navigation Panel (Left side, ~200-250px fixed width)
Account Selector:
- Dropdown showing current account email
- Click to show list of all accounts
- Switch between accounts
Navigation Links:
- "All Inboxes": Unified inbox view across all accounts
- "Labels" section:
- Section header (expandable/collapsible)
- List of top 10 labels below header
- Each label shows name and unread count badge
- "View all labels" link if more than 10 labels exist
- "Contacts": Navigate to contacts list
- "Files": Browse all attachments
Visual Requirements:
- Each navigation item shows unread count (if applicable)
- Active/selected item highlighted
- Hover states for interactive elements
Column 2: List View (Middle, flexible width)
Email List View (when folder/label selected):
-
Virtual scrolling list (render only visible items)
-
Each email item displays across two rows:
First row:
- Checkbox (for multi-select, unchecked by default)
- Star icon (☆ for unstarred, ★ for starred)
- Subject line (bold if unread, regular weight if read)
Second row (indented to align with subject):
- Sender name/email (gray text, smaller font)
- Humanized timestamp (right-aligned, gray text, smaller font):
- Time if today (e.g., "2:30 PM")
- Short date if this year (e.g., "Jan 15")
- Full date if older (e.g., "Jan 15, 2023")
Visual separators:
- 1px horizontal separator between each email item
Optional future enhancements (not required in initial implementation):
- Body snippet (first ~100 characters, plain text)
- Paperclip icon (if has attachments)
- Label tags/chips (if labeled)
-
Click on item → show in Column 3
-
Sort controls (optional for future):
- Date (newest first / oldest first)
- Sender (alphabetical)
- Subject (alphabetical)
Contact List View (when Contacts selected):
- Virtual scrolling list
- Each contact item displays:
- Contact name (or email if no name)
- Email address
- Last interaction date (optional)
- Click on contact → show in Column 3
- Search/filter input at top
Hidden when "Files" is selected (see Files View below)
Column 3: Detail View (Right, flexible width)
Email Detail View:
-
Header Section:
- Subject (large, prominent text)
- From: sender name and email
- To: recipient list (expandable if many recipients)
- Cc: (if any, expandable)
- Date and time (full format)
- Action buttons:
- Reply
- Reply All
- Forward
- Delete
- Archive
- Star/Unstar
- Add Label
-
Body Section:
- Email body (HTML rendering or plain text)
- Scrollable if content is long
- Preserve formatting
-
Attachments Section (if any):
- List of attachments
- Each shows: icon (based on file type), filename, size
- Action: View/Download button
Contact Detail View:
-
Contact Info:
- Name (large text)
- Email address
- Additional fields (if extended later: phone, address)
- Action buttons: Compose Email, Edit, Delete
-
Recent Emails:
- List of last 10 emails with this contact
- Click to view email details
Hidden when "Files" is selected (see Files View below)
Files View (Special Layout)
When "Files" is selected from Column 1:
- Columns 2 and 3 merge into single wide area
- Display all attachments across all emails
Layout:
- Virtual scrolling grid or list view
- Each attachment item shows:
- File type icon (PDF, image, doc, etc.)
- Filename
- File size
- Source email subject (truncated)
- Date received
- Controls:
- Filter by file type dropdown (All, Documents, Images, Archives, etc.)
- Sort by: Name, Size, Date
- Search by filename (optional for future)
Top Navigation Bar
Search Bar:
- Prominent position (center or right side)
- Search by email subject (Phase 1)
- Shows results as-you-type or on Enter key
- Results displayed in Column 2 (email list format)
- Clear button to exit search
App Actions:
- Settings icon/button
- Sync/Refresh icon
- Other utility buttons as needed
Virtual Scrolling Implementation
Performance Requirements
All long lists must use virtual scrolling to maintain 60fps:
What is Virtual Scrolling:
- Only render items currently visible in viewport
- Calculate which items should be visible based on scroll position
- Maintain buffer of items above/below viewport for smooth scrolling
Implementation Strategy:
- Track scroll position (offset from top)
- Calculate visible range:
- `first_visible_index = scroll_offset / item_height`
- `last_visible_index = (scroll_offset + viewport_height) / item_height`
- Add buffer: render items `first - buffer` to `last + buffer`
- Use buffer of ~10-20 items beyond visible range
Target Lists:
- Email list (Column 2) - ~50px per item
- Contact list (Column 2) - ~40px per item
- Files grid (Columns 2+3 merged) - variable item size
Virtual List Component
Create reusable virtual list component: ```rust struct VirtualList<T> { items: Vec<T>, item_height: f32, viewport_height: f32, scroll_offset: f32, buffer_items: usize, } ```
State Management
Application State
```rust
struct AppState {
// Current selections
selected_account_id: Option
// Current view
current_view: View, // Emails, Contacts, Files
// Data
accounts: Vec<Account>,
current_email_list: Vec<Email>,
current_email: Option<Email>,
labels: Vec<Label>,
// UI state
search_query: String,
is_loading: bool,
scroll_positions: HashMap<String, f32>,
// Communication
worker_tx: Sender<WorkerRequest>,
worker_rx: Receiver<WorkerResponse>,
} ```
Worker Communication
```rust enum WorkerRequest { FetchEmails { folder_id: i64 }, FetchEmailDetail { email_id: i64 }, SearchEmails { query: String }, FetchContacts { account_id: i64 }, MarkAsRead { email_id: i64 }, ToggleStar { email_id: i64 }, // ... other operations }
enum WorkerResponse { EmailsList(Vec<Email>), EmailDetail(Email), SearchResults(Vec<Email>), ContactsList(Vec<Contact>), OperationComplete, Error(String), } ```
Database Layer
Abstraction
- No SQL in UI code: All database queries in separate module
- Repository Pattern or similar abstraction
- All queries return Rust structs (from models in #173)
Database Connection
- Connect to database at startup using path from `get_db_path()`
- Open in read-write mode (for marking emails read, starring, etc.)
- Enable SQLite connection pooling if needed for worker threads
Example Repository Functions
```rust impl EmailRepository { fn new(db_path: PathBuf) -> Result<Self>; fn get_emails_by_folder(&self, folder_id: i64) -> Result<Vec<Email>>; fn get_email_by_id(&self, email_id: i64) -> Result<Email>; fn search_by_subject(&self, query: &str) -> Result<Vec<Email>>; fn mark_as_read(&self, email_id: i64) -> Result<()>; fn toggle_star(&self, email_id: i64) -> Result<()>; // ... more operations } ```
Use models from #173: Same Rust structs (Account, Email, Contact, etc.)
Features Checklist
Core Features
- [ ] Database existence check on startup
- [ ] Graceful error handling if database not found
- [ ] Account switching (dropdown in Column 1)
- [ ] Folder/label navigation
- [ ] Email list with virtual scrolling (Column 2)
- [ ] Email detail view (Column 3)
- [ ] Contact list and detail view
- [ ] Files/attachments browser (merged columns)
- [ ] Search by email subject
- [ ] Responsive layout (handle window resize)
Email Actions
- [ ] Mark email as read/unread
- [ ] Star/unstar emails
- [ ] Select and view email
- [ ] View attachments metadata
UI/UX
- [ ] Virtual scrolling maintains 60fps
- [ ] Loading indicators during data fetch
- [ ] Smooth transitions between views
- [ ] Keyboard navigation (optional: arrow keys, Enter to open)
- [ ] Unread count badges
- [ ] Smart timestamp formatting
Code Structure
``` src/ ├── main.rs # Entry point, egui setup, DB path check ├── db/ │ ├── mod.rs │ ├── models.rs # Rust structs (same as #173) │ └── repository.rs # Database operations ├── ui/ │ ├── mod.rs │ ├── app.rs # Main app state and egui App trait │ ├── navigation.rs # Column 1: Navigation panel │ ├── email_list.rs # Column 2: Email list view │ ├── email_detail.rs # Column 3: Email detail view │ ├── contacts.rs # Contact list and detail views │ ├── files.rs # Files browser view │ ├── search.rs # Search bar and functionality │ └── virtual_list.rs # Reusable virtual scrolling component └── worker/ ├── mod.rs # Worker thread logic └── messages.rs # Request/Response types ```
Implementation Phases
Phase 1: Foundation
- [ ] Project setup with egui
- [ ] Database path detection and existence check
- [ ] Database connection with error handling
- [ ] Basic repository layer
- [ ] Worker thread with message passing
Phase 2: Basic UI
- [ ] 3-column layout skeleton
- [ ] Navigation panel (Column 1)
- [ ] Email list (Column 2, simple version without virtual scrolling)
- [ ] Email detail view (Column 3)
- [ ] Account switching
Phase 3: Advanced Features
- [ ] Virtual scrolling for email list
- [ ] Contact views (list + detail)
- [ ] Files browser
- [ ] Search functionality
- [ ] Email actions (mark read, star)
Phase 4: Polish
- [ ] Loading states and error handling
- [ ] Performance optimization
- [ ] UI refinements (colors, spacing, icons)
- [ ] Keyboard shortcuts (optional)
Performance Targets
- Startup time: <2 seconds (including DB connection)
- Email list rendering: 60fps while scrolling through 2000 items
- Search response: <100ms for subject search
- View switching: <50ms to switch between folders/views
- Memory usage: <200MB for app with full dataset
- Frame rate: Maintain 60fps during normal interaction
Success Criteria
- [ ] App checks for database and handles missing DB gracefully
- [ ] All features from checklist implemented
- [ ] Virtual scrolling performs smoothly with 2000+ emails
- [ ] UI remains responsive during all database operations
- [ ] Search returns results quickly (<100ms)
- [ ] App handles window resize gracefully
- [ ] Clean separation between UI and database layers
- [ ] Code is well-structured for future modifications
Technical Stack
- Language: Rust (latest stable)
- GUI Framework: egui
- Database: SQLite3 (via rusqlite)
- Threading: std::thread + mpsc channels
- Additional Crates:
- `eframe` - egui framework integration
- `rusqlite` - SQLite interface
- `dirs` - Platform-specific directory paths
- `serde` / `serde_json` - Serialization
- `chrono` - Date/time handling for timestamps
- `anyhow` or `thiserror` - Error handling
Documentation
- [ ] README with setup instructions
- [ ] Instructions to run fake data generator first
- [ ] Database location documentation
- [ ] Code comments for complex logic
- [ ] Architecture overview
- [ ] Screenshots of the UI
- [ ] Performance benchmarks