commit cf21a84263e1f73952643d7bee351f82d24bf720 Author: Matthieu MALVACHE Date: Wed Dec 10 17:54:22 2025 +0100 Initial release: JMAP Webmail Client A modern, privacy-focused webmail client built with Next.js and the JMAP protocol. Designed for Stalwart Mail Server. Features: - Full email operations (compose, reply, forward, threading) - Real-time push notifications - Dark/light theme support - Mobile responsive design - Keyboard shortcuts - Drag-and-drop organization - i18n (English/French) - Security-first (external content blocked, HTML sanitization) diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..5b695bed --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# JMAP Webmail Configuration +# Copy this file to .env.local and fill in your values + +# App name displayed in the UI +NEXT_PUBLIC_APP_NAME=JMAP Webmail + +# JMAP server URL (required) +# This is the URL of your JMAP-compatible mail server +NEXT_PUBLIC_JMAP_SERVER_URL=https://your-jmap-server.com diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..5dd01922 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env* +!.env.example + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# claude code +.claude/ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..58f8aa20 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx tsc --noEmit && npx eslint . --ext .ts,.tsx diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..d430667c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,190 @@ +# Contributing to JMAP Webmail + +Thank you for your interest in contributing to JMAP Webmail! This document provides guidelines and information for contributors. + +## Getting Started + +### Development Setup + +1. **Fork and clone** the repository: + ```bash + git clone https://github.com/root-fr/jmap-webmail.git + cd jmap-webmail + ``` + +2. **Install dependencies**: + ```bash + npm install + ``` + +3. **Set up environment**: + ```bash + cp .env.example .env.local + # Edit .env.local with your JMAP server URL + ``` + +4. **Start development server**: + ```bash + npm run dev + ``` + +### Code Quality + +Before submitting a pull request, ensure your code passes all checks: + +```bash +# Type checking +npm run typecheck + +# Linting +npm run lint + +# Fix lint issues automatically +npm run lint:fix +``` + +These checks run automatically on commit via Husky pre-commit hooks. + +## Code Style Guidelines + +### TypeScript + +- Use TypeScript for all new code +- Define proper types and interfaces +- Avoid `any` types when possible +- Use meaningful variable and function names + +### React Components + +- Use functional components with hooks +- Keep components focused and single-purpose +- Extract reusable logic into custom hooks +- Place components in appropriate directories under `/components` + +### Styling + +- Use Tailwind CSS utility classes +- Follow the existing design system +- Support both dark and light themes +- Use CSS variables for theme colors + +## Internationalization (i18n) + +This project uses **next-intl** for internationalization. Please follow these guidelines: + +### Key Rules + +1. **Never hardcode user-facing text** - Always use translations: + ```tsx + const t = useTranslations('namespace'); + return
{t('key')}
; + ``` + +2. **Translation file locations**: + - English: `/locales/en/common.json` + - French: `/locales/fr/common.json` + +3. **Namespace organization**: + - `login.*` - Login page strings + - `sidebar.*` - Sidebar navigation + - `email_list.*` - Email list component + - `email_viewer.*` - Email viewer component + - `email_composer.*` - Email composer + - `common.*` - Shared strings + - `notifications.*` - Toast/alert messages + - `settings.*` - Settings page + +4. **Adding new strings**: + - Add to **both** English and French translation files + - Use descriptive, hierarchical keys + - Keep translations consistent in tone + +5. **Locale-aware navigation**: + ```tsx + router.push(`/${params.locale}/settings`); + ``` + +## Pull Request Process + +### Before Submitting + +1. **Create a feature branch**: + ```bash + git checkout -b feature/your-feature-name + ``` + +2. **Make your changes** following the code style guidelines + +3. **Test your changes** thoroughly + +4. **Update translations** if you added user-facing text + +5. **Run all checks**: + ```bash + npm run typecheck && npm run lint + ``` + +### Submitting + +1. **Push your branch** to your fork + +2. **Open a Pull Request** with: + - Clear title describing the change + - Description of what was changed and why + - Screenshots for UI changes + - Reference to any related issues + +### Commit Message Convention + +Follow the conventional commits format: + +- `feat:` - New features +- `fix:` - Bug fixes +- `docs:` - Documentation changes +- `style:` - Code style changes (formatting, etc.) +- `refactor:` - Code refactoring +- `test:` - Adding or updating tests +- `chore:` - Maintenance tasks + +Examples: +``` +feat: add email threading support +fix: resolve attachment download issue +docs: update README with keyboard shortcuts +``` + +## Project Structure + +``` +jmap-webmail/ +├── app/ # Next.js App Router pages +│ └── [locale]/ # Locale-aware routing +├── components/ # React components +│ ├── email/ # Email-related components +│ ├── layout/ # Layout components +│ ├── settings/ # Settings components +│ └── ui/ # Reusable UI components +├── contexts/ # React contexts +├── hooks/ # Custom React hooks +├── lib/ # Utilities and libraries +│ └── jmap/ # JMAP client implementation +├── locales/ # Translation files +│ ├── en/ # English translations +│ └── fr/ # French translations +└── stores/ # Zustand state stores +``` + +## Security + +- **Never commit sensitive data** (API keys, passwords, etc.) +- **Sanitize user input** and email content +- **Block external content** by default for privacy +- Report security vulnerabilities privately + +## Questions? + +If you have questions about contributing, feel free to: +- Open an issue for discussion +- Check existing issues and pull requests + +Thank you for helping improve JMAP Webmail! diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..783256c1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Matthieu MALVACHE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..1c4a650b --- /dev/null +++ b/README.md @@ -0,0 +1,163 @@ +# JMAP Webmail + +A modern, privacy-focused webmail client built with Next.js and the JMAP protocol. + +## Built for Stalwart + +This webmail client is designed to work seamlessly with [**Stalwart Mail Server**](https://stalw.art/) - a modern, secure, and blazingly fast mail server written in Rust. + +**Why Stalwart?** +- **Modern Architecture**: Built from the ground up with Rust for performance and safety +- **JMAP-Native**: First-class support for the JMAP protocol (not just IMAP/SMTP bolted on) +- **Privacy-Focused**: Self-hosted, no third-party dependencies, full control over your data +- **Feature-Rich**: Supports JMAP, IMAP, SMTP, ManageSieve, and more + +[Stalwart GitHub](https://github.com/stalwartlabs/mail-server) | [Documentation](https://stalw.art/docs/) + +## Features + +### Core Email Operations +- Read, compose, reply, reply-all, and forward emails +- Full HTML email rendering with security sanitization +- Attachment upload and download +- Draft auto-save with discard confirmation +- Email threading with Gmail-style inline expansion +- Mark as read/unread, star/unstar +- Archive and delete with configurable behavior +- Color tags/labels for email organization +- Full-text search + +### User Interface +- Clean, minimalist three-pane layout +- Dark and light theme support +- Responsive design for mobile and desktop +- Keyboard shortcuts for power users +- Drag-and-drop email organization +- Right-click context menus +- Smooth animations and transitions +- Infinite scroll pagination + +### Real-time Updates +- Push notifications via JMAP EventSource +- Real-time unread counts +- Live email arrival notifications +- Connection status indicator + +### Security & Privacy +- External content blocked by default +- HTML sanitization with DOMPurify +- SPF/DKIM/DMARC status indicators +- No password storage (session-based auth) +- Shared folder support with proper permissions + +### Internationalization +- English and French language support +- Automatic browser language detection +- Persistent language preference + +## Tech Stack + +- **Framework**: [Next.js 16](https://nextjs.org/) with App Router +- **Language**: TypeScript +- **Styling**: [Tailwind CSS v4](https://tailwindcss.com/) +- **State Management**: [Zustand](https://zustand-demo.pmnd.rs/) +- **JMAP Client**: [jmap-jam](https://www.npmjs.com/package/jmap-jam) +- **i18n**: [next-intl](https://next-intl-docs.vercel.app/) +- **Icons**: [Lucide React](https://lucide.dev/) + +## Getting Started + +### Prerequisites + +- Node.js 18+ +- A JMAP-compatible mail server (we recommend [Stalwart](https://stalw.art/)) + +### Installation + +```bash +# Clone the repository +git clone https://github.com/root-fr/jmap-webmail.git +cd jmap-webmail + +# Install dependencies +npm install + +# Copy environment configuration +cp .env.example .env.local +``` + +### Configuration + +Edit `.env.local` with your settings: + +```env +# App name displayed in the UI +NEXT_PUBLIC_APP_NAME=My Webmail + +# Your JMAP server URL +NEXT_PUBLIC_JMAP_SERVER_URL=https://mail.example.com +``` + +### Development + +```bash +# Start development server +npm run dev + +# Type checking +npm run typecheck + +# Linting +npm run lint +``` + +### Production Build + +```bash +# Build for production +npm run build + +# Start production server +npm start +``` + +## Keyboard Shortcuts + +| Key | Action | +|-----|--------| +| `j` / `k` | Navigate between emails | +| `Enter` / `o` | Open selected email | +| `Esc` | Close viewer / deselect | +| `c` | Compose new email | +| `r` | Reply | +| `R` / `a` | Reply all | +| `f` | Forward | +| `s` | Toggle star | +| `e` | Archive | +| `#` / `Delete` | Delete | +| `u` | Mark as unread | +| `/` | Focus search | +| `x` | Expand/collapse thread | +| `?` | Show shortcuts help | + +## Screenshots + +*Coming soon* + +## Contributing + +We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. + +## Roadmap + +See [ROADMAP.md](ROADMAP.md) for planned features and development status. + +## Acknowledgments + +- [Stalwart Labs](https://stalw.art/) for creating an excellent JMAP mail server +- The [JMAP](https://jmap.io/) working group for the protocol specification +- All contributors and users of this project + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 00000000..49e098f9 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,133 @@ +# JMAP Webmail - Roadmap + +This document tracks the development status and planned features for JMAP Webmail. + +## Completed Features + +### Core Infrastructure +- [x] Next.js 16 with TypeScript and App Router +- [x] Tailwind CSS v4 with Oxide engine +- [x] Zustand state management +- [x] JMAP client implementation (jmap-jam) + +### Authentication +- [x] Login with JMAP server authentication +- [x] Session management (no password storage for security) +- [x] Username autocomplete with history +- [x] Logout functionality +- [x] Authentication error handling + +### JMAP Server Connection +- [x] Session establishment and keep-alive +- [x] Connection error handling and retries +- [x] Storage quota display +- [x] Server capability detection +- [x] Shared folders support (multi-account access) + +### Email Operations +- [x] Email fetching and display +- [x] Full HTML email rendering +- [x] Compose, reply, reply-all, forward +- [x] Draft auto-save with discard confirmation +- [x] Mark as read/unread +- [x] Star/unstar emails +- [x] Delete and archive +- [x] Color tags/labels +- [x] Full-text search +- [x] Attachment upload and download +- [x] Batch operations (multi-select) +- [x] Quick reply form +- [x] Email threading (Gmail-style inline expansion) + +### Real-time Updates +- [x] EventSource for JMAP push notifications +- [x] State synchronization +- [x] Email arrival notifications +- [x] Real-time unread counts +- [x] Mailbox change handling + +### User Interface +- [x] Three-pane layout (sidebar, list, viewer) +- [x] Minimalist design system +- [x] Dark and light theme support +- [x] Custom scrollbars +- [x] Mobile responsive design +- [x] Keyboard shortcuts +- [x] Drag-and-drop email organization +- [x] Right-click context menus +- [x] Hierarchical mailbox display +- [x] Email list with avatars and visual hierarchy +- [x] Expandable email headers +- [x] External content warning banner +- [x] SPF/DKIM/DMARC status indicators +- [x] Loading states and skeletons +- [x] Smooth transitions and animations +- [x] Infinite scroll pagination +- [x] Error boundaries +- [x] Settings page with preferences + +### Internationalization +- [x] English language support +- [x] French language support +- [x] Automatic browser language detection +- [x] Language preference persistence + +### Security +- [x] External content blocked by default +- [x] HTML sanitization with DOMPurify +- [x] User control for loading external content + +## Planned Features + +### Address Book & Contacts +- [ ] Contact store with CRUD operations +- [ ] Contacts list view with search/filter +- [ ] Contact details view/edit form +- [ ] Contact groups management +- [ ] vCard import/export +- [ ] JMAP contacts sync (if server supports) +- [ ] Email autocomplete from contacts +- [ ] Contacts integration in composer + +### Advanced Features +- [ ] Email filters and rules +- [ ] Calendar integration (JMAP Calendars) +- [ ] Email templates +- [ ] Signature management +- [ ] Vacation responder settings +- [ ] Email aliases support +- [ ] Advanced search with filters +- [ ] Email encryption (PGP/GPG) + +### Performance Optimizations +- [ ] Virtual scrolling for large lists +- [ ] Email content caching +- [ ] Bundle size optimization +- [ ] Service worker for offline support +- [ ] Lazy loading for attachments + +### Testing +- [ ] Unit tests for utilities +- [ ] Component tests +- [ ] E2E tests with Playwright +- [ ] Accessibility testing +- [ ] Performance testing + +### Deployment +- [ ] Health check endpoint +- [ ] Production build optimizations +- [ ] Monitoring and logging + +### Security Enhancements +- [ ] CSP headers configuration +- [ ] Additional XSS protection layers +- [ ] Rate limiting +- [ ] CORS configuration + +## Known Issues + +- [ ] Next.js workspace root warning (cosmetic) + +## Contributing + +Want to help implement a feature? Check out our [CONTRIBUTING.md](CONTRIBUTING.md) guide! diff --git a/app/[locale]/error.tsx b/app/[locale]/error.tsx new file mode 100644 index 00000000..b5c7ae6b --- /dev/null +++ b/app/[locale]/error.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { useEffect } from "react"; +import { useTranslations } from "next-intl"; +import { AlertCircle, RefreshCw, Home } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { useParams, useRouter } from "next/navigation"; + +/** + * Route-level error boundary for locale pages. + * Catches errors in the locale layout and its children. + */ +export default function LocaleError({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + const t = useTranslations("errors"); + const params = useParams(); + const router = useRouter(); + + useEffect(() => { + console.error("Route error:", error); + }, [error]); + + return ( +
+
+
+ +
+

+ {t("page_error_title")} +

+

+ {t("page_error_description")} +

+
+ + +
+
+
+ ); +} diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx new file mode 100644 index 00000000..8ee5d7ea --- /dev/null +++ b/app/[locale]/layout.tsx @@ -0,0 +1,77 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import { notFound } from "next/navigation"; +import { NextIntlClientProvider } from "next-intl"; +import { ThemeProvider } from "@/components/providers/theme-provider"; +import { locales } from "@/i18n/request"; +import "../globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "JMAP Webmail", + description: "Minimalist webmail client using JMAP protocol", +}; + +export default async function LocaleLayout({ + children, + params +}: { + children: React.ReactNode; + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + + // Validate that the incoming `locale` parameter is valid + if (!(locales as readonly string[]).includes(locale)) notFound(); + + // Load messages for the current locale + let messages; + try { + messages = (await import(`@/locales/${locale}/common.json`)).default; + } catch { + notFound(); + } + + return ( + + +