# EMLy Application Documentation EMLy is a desktop email viewer application built for 3gIT, designed to open and display `.eml` and `.msg` email files on Windows. It provides a modern, user-friendly interface for viewing email content, attachments, and metadata. ## Table of Contents 1. [Architecture Overview](#architecture-overview) 2. [Technology Stack](#technology-stack) 3. [Project Structure](#project-structure) 4. [Backend (Go)](#backend-go) 5. [Frontend (SvelteKit)](#frontend-sveltekit) 6. [State Management](#state-management) 7. [Internationalization (i18n)](#internationalization-i18n) 8. [UI Components](#ui-components) 9. [Key Features](#key-features) 10. [Build & Development](#build--development) --- ## Architecture Overview EMLy is built using the **Wails v2** framework, which combines a Go backend with a web-based frontend. This architecture allows: - **Go Backend**: Handles file operations, Windows API calls, email parsing, and system interactions - **Web Frontend**: Provides the user interface using SvelteKit with Svelte 5 - **Bridge**: Wails automatically generates TypeScript bindings for Go functions, enabling seamless communication ``` ┌─────────────────────────────────────────────────────────┐ │ EMLy Application │ ├─────────────────────────────────────────────────────────┤ │ Frontend (SvelteKit + Svelte 5) │ │ ├── Routes & Pages │ │ ├── UI Components (shadcn-svelte) │ │ ├── State Management (Svelte 5 Runes) │ │ └── i18n (ParaglideJS) │ ├─────────────────────────────────────────────────────────┤ │ Wails Bridge (Auto-generated TypeScript bindings) │ ├─────────────────────────────────────────────────────────┤ │ Backend (Go) │ │ ├── App Logic (app.go) │ │ ├── Email Parsing (backend/utils/mail/) │ │ ├── Windows APIs (screenshot, debugger detection) │ │ └── File Operations │ └─────────────────────────────────────────────────────────┘ ``` --- ## Technology Stack ### Backend - **Go 1.21+**: Primary backend language - **Wails v2**: Desktop application framework - **Windows Registry API**: For checking default file handlers - **GDI/User32 APIs**: For screenshot functionality ### Frontend - **SvelteKit**: Application framework - **Svelte 5**: UI framework with Runes ($state, $derived, $effect, $props) - **TypeScript**: Type-safe JavaScript - **shadcn-svelte**: UI component library - **Tailwind CSS**: Utility-first CSS framework - **ParaglideJS**: Internationalization - **Lucide Icons**: Icon library - **Bun**: JavaScript runtime and package manager --- ## Project Structure ``` EMLy/ ├── app.go # Main application logic ├── main.go # Application entry point ├── logger.go # Logging utilities ├── wails.json # Wails configuration ├── backend/ │ └── utils/ │ ├── mail/ │ │ ├── eml_reader.go # EML file parsing │ │ ├── msg_reader.go # MSG file parsing │ │ ├── mailparser.go # MIME email parsing │ │ └── file_dialog.go # File dialog utilities │ ├── screenshot_windows.go # Windows screenshot capture │ ├── debug_windows.go # Debugger detection │ ├── ini-reader.go # Configuration file parsing │ ├── machine-identifier.go # System info collection │ └── file-metadata.go # File metadata utilities ├── frontend/ │ ├── src/ │ │ ├── routes/ # SvelteKit routes │ │ │ ├── +layout.svelte # Root layout │ │ │ ├── +error.svelte # Error page │ │ │ ├── (app)/ # Main app route group │ │ │ │ ├── +layout.svelte # App layout with sidebar │ │ │ │ ├── +page.svelte # Mail viewer page │ │ │ │ └── settings/ │ │ │ │ └── +page.svelte # Settings page │ │ │ ├── image/ # Image viewer route │ │ │ │ ├── +layout.svelte │ │ │ │ └── +page.svelte │ │ │ └── pdf/ # PDF viewer route │ │ │ ├── +layout.svelte │ │ │ └── +page.svelte │ │ ├── lib/ │ │ │ ├── components/ # Svelte components │ │ │ │ ├── MailViewer.svelte │ │ │ │ ├── SidebarApp.svelte │ │ │ │ ├── UnsavedBar.svelte │ │ │ │ └── ui/ # shadcn-svelte components │ │ │ ├── stores/ # State management │ │ │ │ ├── app.ts │ │ │ │ ├── mail-state.svelte.ts │ │ │ │ └── settings.svelte.ts │ │ │ ├── paraglide/ # i18n runtime │ │ │ ├── wailsjs/ # Auto-generated Go bindings │ │ │ ├── types/ # TypeScript types │ │ │ └── utils/ # Utility functions │ │ └── messages/ # i18n translation files │ │ ├── en.json │ │ └── it.json │ ├── static/ # Static assets │ └── package.json └── config.ini # Application configuration ``` --- ## Backend (Go) ### Entry Point (`main.go`) The application starts in `main.go`, which: 1. Initializes the logger 2. Parses command-line arguments for: - `.eml` or `.msg` files to open on startup - `--view-image=` for image viewer mode - `--view-pdf=` for PDF viewer mode 3. Configures Wails with window options 4. Sets up single-instance lock to prevent multiple main windows 5. Binds the `App` struct for frontend access ```go // Single instance with unique ID based on mode uniqueId := "emly-app-lock" if strings.Contains(arg, "--view-image") { uniqueId = "emly-viewer-" + arg windowTitle = "EMLy Image Viewer" } ``` ### Application Core (`app.go`) The `App` struct is the main application controller, exposed to the frontend via Wails bindings. #### Key Properties ```go type App struct { ctx context.Context StartupFilePath string // File opened via command line CurrentMailFilePath string // Currently loaded mail file openImages map[string]bool // Track open image viewers openPDFs map[string]bool // Track open PDF viewers openEMLs map[string]bool // Track open EML viewers } ``` #### Core Methods | Method | Description | |--------|-------------| | `GetConfig()` | Returns application configuration from `config.ini` | | `GetStartupFile()` | Returns file path passed via command line | | `SetCurrentMailFilePath()` | Updates the current mail file path | | `ReadEML(path)` | Parses an EML file and returns email data | | `ReadMSG(path)` | Parses an MSG file and returns email data | | `ReadPEC(path)` | Parses PEC (Italian certified email) files | | `ShowOpenFileDialog()` | Opens native file picker for EML/MSG files | | `OpenImageWindow(data, filename)` | Opens image in new viewer window | | `OpenPDFWindow(data, filename)` | Opens PDF in new viewer window | | `OpenEMLWindow(data, filename)` | Opens EML attachment in new window | | `TakeScreenshot()` | Captures window screenshot as base64 PNG | | `SubmitBugReport(input)` | Creates bug report with screenshot and system info | | `ExportSettings(json)` | Exports settings to JSON file | | `ImportSettings()` | Imports settings from JSON file | | `IsDebuggerRunning()` | Checks if a debugger is attached | | `QuitApp()` | Terminates the application | ### Email Parsing (`backend/utils/mail/`) #### EML Reader (`eml_reader.go`) Reads standard `.eml` files using the `mailparser.go` MIME parser. #### MSG Reader (`msg_reader.go`) Handles Microsoft Outlook `.msg` files using external conversion. #### Mail Parser (`mailparser.go`) A comprehensive MIME email parser that handles: - Multipart messages (mixed, alternative, related) - Text and HTML bodies - Attachments with proper content-type detection - Embedded files (inline images) - Various content transfer encodings (base64, quoted-printable, 7bit, 8bit) The `EmailData` structure returned to the frontend: ```go type EmailData struct { Subject string From string To []string Cc []string Bcc []string Body string // HTML or text body Attachments []AttachmentData IsPec bool // Italian certified email } ``` ### Screenshot Utility (`backend/utils/screenshot_windows.go`) Captures the application window using Windows GDI APIs: - Uses `FindWindowW` to locate window by title - Uses `DwmGetWindowAttribute` for DPI-aware window bounds - Creates compatible DC and bitmap for capture - Returns image as base64-encoded PNG --- ## Frontend (SvelteKit) ### Route Structure SvelteKit uses file-based routing. The `(app)` folder is a route group that applies the main app layout. ``` routes/ ├── +layout.svelte # Root layout (minimal) ├── +error.svelte # Global error page ├── (app)/ # Main app group │ ├── +layout.svelte # App layout with titlebar, sidebar, footer │ ├── +layout.ts # Server data loader │ ├── +page.svelte # Main mail viewer page │ └── settings/ │ ├── +page.svelte # Settings page │ └── +layout.ts # Settings data loader ├── image/ # Standalone image viewer │ ├── +layout.svelte │ └── +page.svelte └── pdf/ # Standalone PDF viewer ├── +layout.svelte └── +page.svelte ``` ### Main App Layout (`(app)/+layout.svelte`) The app layout provides: 1. **Custom Titlebar**: Windows-style titlebar with minimize/maximize/close buttons - Draggable for window movement - Double-click to maximize/restore 2. **Sidebar Provider**: Collapsible navigation sidebar 3. **Footer Bar**: Quick access icons for: - Toggle sidebar - Navigate to home - Navigate to settings - Open bug report dialog - Reload application 4. **Bug Report Dialog**: Complete bug reporting system with: - Screenshot capture on dialog open - Name, email, description fields - System info collection - Creates ZIP archive with all data 5. **Toast Notifications**: Using svelte-sonner 6. **Debugger Protection**: Detects attached debuggers and can quit if detected ### Mail Viewer (`(app)/+page.svelte` + `MailViewer.svelte`) The mail viewer is split into two parts: - `+page.svelte`: Page wrapper that initializes mail state from startup file - `MailViewer.svelte`: Core email viewing component #### MailViewer Features - **Empty State**: Shows "Open EML/MSG File" button when no email loaded - **Email Header**: Displays subject, from, to, cc, bcc fields - **PEC Badge**: Shows green badge for Italian certified emails - **Attachments Bar**: Horizontal scrollable list of attachments with type-specific icons - **Email Body**: Rendered in sandboxed iframe for security - **Loading Overlay**: Shows spinner during file loading #### Attachment Handling ```typescript // Different handlers based on file type if (att.contentType.startsWith("image/")) { // Opens in built-in or external viewer based on settings await OpenImageWindow(base64Data, filename); } else if (att.filename.toLowerCase().endsWith(".pdf")) { // Opens in built-in or external PDF viewer await OpenPDFWindow(base64Data, filename); } else if (att.filename.toLowerCase().endsWith(".eml")) { // Opens in new EMLy instance await OpenEMLWindow(base64Data, filename); } else { // Download as file ... } ``` ### Settings Page (`(app)/settings/+page.svelte`) Organized into cards with various configuration options: 1. **Language Settings** - Radio buttons for English/Italian - Triggers full page reload on change 2. **Export/Import Settings** - Export current settings to JSON file - Import settings from JSON file 3. **Preview Page Settings** - Supported image types (JPG, JPEG, PNG) - Toggle built-in image viewer - Toggle built-in PDF viewer 4. **Danger Zone** (Hidden by default, revealed by clicking settings 10 times rapidly) - Open DevTools hint - Reload application - Reset to defaults - Debugger protection toggle (disabled in production) - Version information display #### Unsaved Changes Detection The settings page tracks changes and shows a persistent toast when there are unsaved modifications: ```typescript $effect(() => { const dirty = !isSameSettings(normalizeSettings(form), lastSaved); unsavedChanges.set(dirty); if (dirty) { showUnsavedChangesToast({ onSave: saveToStorage, onReset: resetToLastSaved, }); } }); ``` --- ## State Management EMLy uses a combination of Svelte 5 Runes and traditional Svelte stores. ### Mail State (`stores/mail-state.svelte.ts`) Uses Svelte 5's `$state` rune for reactive email data: ```typescript class MailState { currentEmail = $state(null); setParams(email: internal.EmailData | null) { this.currentEmail = email; } clear() { this.currentEmail = null; } } export const mailState = new MailState(); ``` ### Settings Store (`stores/settings.svelte.ts`) Manages application settings with localStorage persistence: ```typescript class SettingsStore { settings = $state({ ...defaults }); hasHydrated = $state(false); load() { /* Load from localStorage */ } save() { /* Save to localStorage */ } update(newSettings: Partial) { /* Merge and save */ } reset() { /* Reset to defaults */ } } ``` Settings schema: ```typescript interface EMLy_GUI_Settings { selectedLanguage: "en" | "it"; useBuiltinPreview: boolean; useBuiltinPDFViewer: boolean; previewFileSupportedTypes: string[]; enableAttachedDebuggerProtection: boolean; } ``` ### App Store (`stores/app.ts`) Traditional Svelte writable stores for UI state: ```typescript export const dangerZoneEnabled = writable(false); export const unsavedChanges = writable(false); export const sidebarOpen = writable(true); export const bugReportDialogOpen = writable(false); export const events = writable([]); ``` --- ## Internationalization (i18n) EMLy uses **ParaglideJS** for compile-time type-safe translations. ### Translation Files Located in `frontend/messages/`: - `en.json` - English translations - `it.json` - Italian translations ### Message Format ```json { "$schema": "https://inlang.com/schema/inlang-message-format", "mail_no_email_selected": "No email selected", "mail_open_eml_btn": "Open EML/MSG File", "settings_title": "Settings", "settings_language_english": "English", "settings_language_italian": "Italiano" } ``` ### Usage in Components ```typescript import * as m from "$lib/paraglide/messages"; // In template

{m.settings_title()}

``` ### Changing Language ```typescript import { setLocale } from "$lib/paraglide/runtime"; await setLocale("it", { reload: false }); location.reload(); // Page reload required for full update ``` --- ## UI Components EMLy uses **shadcn-svelte**, a port of shadcn/ui for Svelte. Components are located in `frontend/src/lib/components/ui/`. ### Available Components | Component | Usage | |-----------|-------| | `Button` | Primary buttons with variants (default, destructive, outline, ghost) | | `Card` | Container with header, content, footer sections | | `Dialog` | Modal dialogs for bug reports, confirmations | | `AlertDialog` | Confirmation dialogs with cancel/continue actions | | `Switch` | Toggle switches for boolean settings | | `Checkbox` | Multi-select checkboxes | | `RadioGroup` | Single-select options (language selection) | | `Label` | Form labels | | `Input` | Text input fields | | `Textarea` | Multi-line text input | | `Separator` | Visual dividers | | `Sidebar` | Collapsible navigation sidebar | | `Tooltip` | Hover tooltips | | `Sonner` | Toast notifications | | `Badge` | Status badges | | `Skeleton` | Loading placeholders | ### Custom Components | Component | Purpose | |-----------|---------| | `MailViewer.svelte` | Email display with header, attachments, body | | `SidebarApp.svelte` | Navigation sidebar with menu items | | `UnsavedBar.svelte` | Unsaved changes notification bar | --- ## Key Features ### 1. Email Viewing - Parse and display EML and MSG files - Show email metadata (from, to, cc, bcc, subject) - Render HTML email bodies in sandboxed iframe - List and handle attachments with type-specific actions ### 2. Attachment Handling - **Images**: Open in built-in viewer or external app - **PDFs**: Open in built-in viewer or external app - **EML files**: Open in new EMLy window - **Other files**: Download directly ### 3. Multi-Window Support The app can spawn separate viewer windows: - Image viewer (`--view-image=`) - PDF viewer (`--view-pdf=`) - EML viewer (new app instance with file path) Each viewer has a unique instance ID to allow multiple concurrent viewers. ### 4. Bug Reporting Complete bug reporting system: 1. Captures screenshot when dialog opens 2. Collects user input (name, email, description) 3. Includes current mail file if loaded 4. Gathers system information 5. Creates ZIP archive in temp folder 6. Shows path and allows opening folder ### 5. Settings Management - Language selection (English/Italian) - Built-in viewer preferences - Supported file type configuration - Export/import settings as JSON - Reset to defaults ### 6. Security Features - Debugger detection and protection - Sandboxed iframe for email body - Single-instance lock for main window - Disabled link clicking in email body ### 7. PEC Support Special handling for Italian Posta Elettronica Certificata (PEC): - Detects PEC emails - Shows signed mail badge - Handles P7S signature files - Processes daticert.xml metadata --- ## Build & Development ### Prerequisites - Go 1.21+ - Node.js 18+ or Bun - Wails CLI v2 ### Development ```bash # Install frontend dependencies cd frontend && bun install # Run in development mode wails dev ``` ### Building ```bash # Build for Windows wails build -platform windows/amd64 # Output: build/bin/EMLy.exe ``` ### Configuration `wails.json` configures the build: ```json { "name": "EMLy", "frontend:install": "bun install", "frontend:build": "bun run build", "frontend:dev:watcher": "bun run dev", "fileAssociations": [ { "ext": "eml", "name": "Email Message", "description": "EML File" } ] } ``` ### File Association The app registers as a handler for `.eml` files via Windows file associations, configured in `wails.json`. --- ## Wails Bindings Wails automatically generates TypeScript bindings for Go functions. These are located in `frontend/src/lib/wailsjs/`. ### Generated Files - `go/main/App.ts` - TypeScript functions calling Go methods - `go/models.ts` - TypeScript types for Go structs - `runtime/runtime.ts` - Wails runtime functions ### Usage Example ```typescript import { ReadEML, ShowOpenFileDialog } from "$lib/wailsjs/go/main/App"; import type { internal } from "$lib/wailsjs/go/models"; // Open file dialog const filePath = await ShowOpenFileDialog(); // Parse email const email: internal.EmailData = await ReadEML(filePath); ``` ### Runtime Events Wails provides event system for Go-to-JS communication: ```typescript import { EventsOn } from "$lib/wailsjs/runtime/runtime"; // Listen for second instance launch EventsOn("launchArgs", (args: string[]) => { // Handle file opened from second instance }); ``` --- ## Error Handling ### Frontend Toast notifications for user-facing errors: ```typescript import { toast } from "svelte-sonner"; import * as m from "$lib/paraglide/messages"; try { await ReadEML(filePath); } catch (error) { toast.error(m.mail_error_opening()); } ``` ### Backend Errors are returned to frontend and logged: ```go func (a *App) ReadEML(filePath string) (*internal.EmailData, error) { data, err := internal.ReadEmlFile(filePath) if err != nil { Log("Failed to read EML:", err) return nil, err } return data, nil } ``` --- ## Debugging ### Development Mode In dev mode (`wails dev`): - Hot reload enabled - Debug logs visible - DevTools accessible via Ctrl+Shift+F12 - Danger Zone always visible in settings ### Production - Debugger protection can terminate app if debugger detected - Danger Zone hidden by default - Access Danger Zone by clicking settings link 10 times within 4 seconds --- ## License & Credits EMLy is developed by FOISX @ 3gIT.