In Loving Memory of Joe Ipson
This project is dedicated to Joe Ipson, the original creator of DailyNotes, who passed away in the summer of 2025 after a courageous battle with cancer.
Joe was a kindred spirit who believed in the simple power of writing things down. He built DailyNotes to bring the mindful experience of a physical planner into the digital world. His vision was to create something personal, self-hosted, and beautifully simple.
This project continues in his memory. Every commit, every feature, every bug fix is a small tribute to a dear friend whose spirit lives on in the code he wrote and the ideas he shared.
Rest easy, Joe. We'll take it from here.
The idea for this app came from using my Hobonichi Techo planner every morning to write down what I needed to accomplish that day & using it for scratching down random thoughts and notes as the day went on. The closest thing I've seen to an app for replacing this system is Noteplan, but I don't use a Mac or an iOS device, and it's not self-hostable, so I decided to write my own.
To check your current version, open Settings in the app and look in the About section.
Since I had the need for keeping track of to-dos throughout the day, regular Markdown didn't work for me since it doesn't natively support tasks. So as an alternative I'm using Github Flavored Markdown (GFM). I really wanted it to feel like an actual text editor and not just a textbox, so I decided to use CodeMirror to handle all the input. Fira Code is used to provide font ligatures. Some other nice features include code highlighting, text/code folding, and a task list where you can toggle the status of any task from any date or note.
Joe had a vision for what DailyNotes could become before calling it a 1.0 release. I've done my best to interpret and implement those features, along with feature requests from GitHub issues that Joe was considering. Here's what makes DailyNotes a powerful daily planning tool:
- GitHub Flavored Markdown — Full GFM support with task lists (
- [ ]/- [x]), tables, code blocks, and more - CodeMirror Editor — A real text editor experience with syntax highlighting, code folding, and keyboard shortcuts
- Fira Code Font — Beautiful font ligatures for a polished writing experience
- Auto-save — Never lose your work with optional automatic saving
- Data Encryption — All notes encrypted at rest with AES encryption
- Powerful Search — Syntax-based search with
tag:,project:, and full-text queries - Nested Tags — Hierarchical tag organization (e.g.,
work/meetings,home/family) - Kanban Board — Visual task management with drag-and-drop columns
- Task List — View and toggle tasks within each note with one-click status updates
- HTML Preview — Live markdown preview with
Cmd+K V(side-by-side) orShift+Cmd+V(full screen) - Mermaid Diagrams — Create flowcharts, sequence diagrams, ERDs, and more directly in your notes
- Themes — Light, Dark, and System themes to match your environment
- Calendar Feed (ICS) — Subscribe to your notes in Google Calendar, Apple Calendar, or any ICS-compatible app
- External Calendar Support — Display events from external ICS feeds alongside your daily notes
- Self-hosted — Your data stays on your server, under your control
- Docker Ready — Easy deployment with Docker and Docker Compose
- Multi-user Support — Multiple users with separate, encrypted data
- No Vendor Lock-in — Export all your notes as markdown files anytime
- Password Recovery — Reset your password via email if forgotten
- Magic Link Sign-in — Sign in with a secure email link instead of a password
- Email Management — Add or update your email in Settings to enable these features
DailyNotes supports password recovery and passwordless sign-in via email. These features require SMTP configuration (see Environment Variables).
- Click the menu icon (⋮) in the header
- Select Settings
- In the Account section, enter your email address
- Click Add Email
Once configured, you can use password recovery and magic link sign-in.
If you forget your password:
- Go to the login page
- Click Forgot password?
- Enter your email address
- Check your email for a reset link (valid for 1 hour)
- Click the link and enter your new password
Sign in without entering your password:
- Go to the login page
- Click Sign in with email
- Enter your email address
- Check your email for a sign-in link (valid for 15 minutes)
- Click the link to automatically sign in
| Feature | Description |
|---|---|
| Secure tokens | Cryptographically random, SHA-256 hashed |
| Rate limiting | 3 requests per email per hour |
| Short expiration | Reset: 1 hour, Magic link: 15 minutes |
| Single-use tokens | Each token can only be used once |
| Email enumeration prevention | Same response whether email exists or not |
| Encrypted email storage | Email addresses encrypted at rest (AES) |
Gmail (with App Password):
SMTP_HOST: smtp.gmail.com
SMTP_PORT: 587
SMTP_USER: your-email@gmail.com
SMTP_PASSWORD: your-app-password # Generate at myaccount.google.com/apppasswords
SMTP_FROM_NAME: DailyNotes
APP_URL: https://your-dailynotes-instance.comMailgun:
SMTP_HOST: smtp.mailgun.org
SMTP_PORT: 587
SMTP_USER: postmaster@your-domain.mailgun.org
SMTP_PASSWORD: your-mailgun-password
SMTP_FROM_EMAIL: noreply@your-domain.com
APP_URL: https://your-dailynotes-instance.comAmazon SES:
SMTP_HOST: email-smtp.us-east-1.amazonaws.com
SMTP_PORT: 587
SMTP_USER: your-ses-smtp-username
SMTP_PASSWORD: your-ses-smtp-password
SMTP_FROM_EMAIL: noreply@your-verified-domain.com
APP_URL: https://your-dailynotes-instance.comImportant Notes:
- Gmail requires an App Password, not your regular password
- The
APP_URLmust match the URL users access DailyNotes from (for email links to work) - If SMTP is not configured, password recovery and magic link features are automatically disabled
- Users can still sign in with username/password even without email configured
DailyNotes supports Light, Dark, and System themes to match your preferred working environment.
| Theme | Description |
|---|---|
| 🌙 Dark | Default dark interface, optimized for low-light environments |
| ☀️ Light | Clean, bright interface with light backgrounds |
| 💻 System | Automatically follows your operating system's color scheme setting |
- Click the menu icon (⋮) in the header
- Select Settings
- In the Appearance section, click your preferred theme
- The theme changes instantly and is saved for future sessions
The System option automatically switches between light and dark themes based on your OS settings (e.g., macOS Dark Mode, Windows Dark Theme). This is perfect if you prefer dark mode at night and light mode during the day.
DailyNotes supports Mermaid diagrams in the markdown preview, allowing you to create flowcharts, sequence diagrams, class diagrams, and more directly in your notes.
Use a fenced code block with mermaid as the language:
```mermaid
graph TD
A[Start] --> B{Decision}
B -->|Yes| C[Do something]
B -->|No| D[Do something else]
C --> E[End]
D --> E
```Diagrams are rendered in the HTML preview:
- Side-by-side: Press
Cmd+K V(Mac) orCtrl+K V(Windows/Linux) - Preview only: Press
Shift+Cmd+V(Mac) orShift+Ctrl+V(Windows/Linux)
Mermaid supports many diagram types. Here are some examples:
| Diagram Type | Use Case | Example Syntax |
|---|---|---|
| Flowchart | Process flows, decisions | graph TD or graph LR |
| Sequence | API calls, interactions | sequenceDiagram |
| Class | Object relationships | classDiagram |
| State | State machines | stateDiagram-v2 |
| Entity Relationship | Database schemas | erDiagram |
| Gantt | Project timelines | gantt |
| Pie | Data distribution | pie |
| Git Graph | Branch visualization | gitGraph |
Diagrams automatically adapt to your app theme:
- Dark theme: Diagrams render with dark-friendly colors
- Light theme: Diagrams render with light-friendly colors
- Switching themes re-renders diagrams with the appropriate color scheme
If a diagram has syntax errors, DailyNotes displays a helpful error message instead of breaking the preview. This makes it easy to debug and fix diagram issues.
For full syntax documentation and examples, visit the Mermaid documentation.
- Generate or rotate a private read-only ICS URL with
GET/POST /api/calendar_token(requires auth). - Subscribe in Google Calendar via Settings -> Add calendar -> From URL using
/api/calendar.ics?token=<your_token>. - Each daily note becomes an all-day event; the feed updates when notes change (Google polls periodically).
- Rotate the token to immediately revoke previous subscriptions or disable sharing with
DELETE /api/calendar_token. - You can now subscribe to external ICS feeds (e.g., Google private links) in Settings; DailyNotes shows those events on the matching day.
DailyNotes features a powerful syntax-based search that lets you quickly find notes using text queries.
| Syntax | Description | Example |
|---|---|---|
tag:value |
Filter by tag | tag:meeting |
project:value |
Filter by project | project:work |
t:value |
Shorthand for tag | t:1on1 |
p:value |
Shorthand for project | p:DN |
tag:"multi word" |
Quoted values for spaces | tag:"code review" |
| Plain text | Search note content | budget report |
| Query | What it finds |
|---|---|
budget |
All notes containing "budget" |
tag:meeting |
All notes tagged "meeting" |
project:work tag:Q4 |
Notes in "work" project with "Q4" tag |
tag:1on1 tag:feedback |
Notes with both tags (AND) |
project:DN project:personal |
Notes in either project (OR) |
tag:meeting notes agenda |
Tagged "meeting" containing "notes" AND "agenda" |
- Multiple tags = AND (note must have all specified tags)
- Multiple projects = OR (note can be in any specified project)
- Multiple text terms = AND (note must contain all words)
- Autocomplete: Type
tag:orproject:to see suggestions from your existing tags/projects - Keyboard navigation: Use arrow keys to select, Tab/Enter to confirm
- Result highlighting: Matching text is highlighted in search results with context snippets
- Syntax help: Click the
?button for a quick reference
DailyNotes supports hierarchical tag organization using / as a delimiter. This lets you create tag hierarchies like work/meetings, home/family, or projects/dailynotes/frontend.
Use the / character in your frontmatter to create nested tags:
---
title: Weekly Team Sync
tags: work/meetings, work/1on1, home/family
---Nested tags appear as a collapsible tree in the sidebar:
| Display | Description |
|---|---|
▶ work |
Collapsed parent tag (click arrow to expand) |
▼ work |
Expanded parent showing children below |
meetings |
Child tag indented under parent |
1on1 |
Flat tag (no /) displayed inline at top |
- Flat tags (without
/) are displayed inline at the top, wrapping as needed - Parent tags show a collapsible chevron (▶/▼)
- Child tags are displayed inline under their parent when expanded
- Expand/collapse state is saved and persists across page refreshes
Nested tags support hierarchical search - searching for a parent tag matches all its children:
| Search Query | Matches |
|---|---|
tag:work |
Notes with work, work/meetings, work/1on1 |
tag:work/meetings |
Only notes with exactly work/meetings |
tag:home |
Notes with home, home/family, home/tech |
This makes it easy to search broadly (tag:work for all work-related notes) or specifically (tag:work/meetings for just meeting notes).
| Tags in Frontmatter | Sidebar Display |
|---|---|
tags: meeting, 1on1, review |
meeting 1on1 review (inline) |
tags: work/meetings, work/reviews |
▼ work → meetings reviews |
tags: home/tech, home/family, work |
▼ home → family tech, work |
- Existing tags are unchanged: Tags without
/continue to work exactly as before - Autocomplete works: Type
tag:work/to see suggestions for nested tags - Mix and match: You can use both flat and nested tags in the same note
- Deep nesting: Multiple levels work too:
projects/dailynotes/frontend/components
DailyNotes includes an optional Kanban board view for organizing tasks with drag-and-drop support.
- Click the menu icon (⋮) in the header
- Select Settings
- Find the Kanban section
- Toggle Enable Kanban board
When enabled, the Tasks icon in the header changes to a columns icon. Click it to open the Kanban modal.
Use the >>column syntax at the end of any task to assign it to a specific column:
| Task Syntax | Column Assignment |
|---|---|
- [ ] Plain task |
Defaults to "todo" |
- [x] Completed task |
Defaults to "done" |
- [ ] In progress >>doing |
Explicit "doing" column |
- [x] Done but in review >>review |
Stays in "review" (explicit wins) |
Column names support letters, numbers, and hyphens: >>in-progress, >>stage-2, >>Q4
| Setting | Default Value |
|---|---|
| Default columns | todo, done |
| Configurable in | Settings → Kanban |
Add, remove, or reorder columns in the Settings panel.
Override columns for a specific note using YAML frontmatter:
---
title: Sprint Planning
kanban:
- backlog
- in-progress
- review
- done
---
- [ ] Design mockups >>backlog
- [ ] Implement API >>in-progress
- [ ] Write tests >>reviewIf you use a column that doesn't exist in your configuration:
| Scenario | Result |
|---|---|
Columns: [todo, done] |
Default setup |
Task uses >>staging |
Columns become [todo, staging, done] |
Task uses >>review |
Columns become [todo, staging, review, done] |
New columns are automatically inserted before "done". To reorder, update the columns in Settings or note frontmatter.
- Drag any task card to move it between columns
- The task's
>>columnsyntax is automatically updated in the markdown - Changes are saved immediately to the note
| Condition | Resulting Column |
|---|---|
No >>column + unchecked [ ] |
todo |
No >>column + checked [x] |
done |
Explicit >>column (any checkbox state) |
The specified column |
Key point: Explicit >>column syntax always takes precedence over the checkbox state. This lets you have completed tasks in a "review" column.
Here are some screenshots of what it looks like:
Main editor:
Search page:
Task list:
The recommended way of running is to pull the image from Docker Hub.
| Environment Variable | Description | Default |
|---|---|---|
| API_SECRET_KEY | Used to sign API tokens. | Will be generated automatically if not passed in. |
| DATABASE_URI | Connection string for DB. | Will create and use a SQLite DB if not passed in. |
| DB_ENCRYPTION_KEY | Secret key for encrypting data. Length must be a multiple of 16. Warning: If changed data will not be able to be decrypted! |
Will be generated automatically if not passed in. |
| PREVENT_SIGNUPS | Disable signup form? Anything in this variable will prevent signups. | False |
| BASE_URL | Used when using a subfolder on a reverse proxy | None |
| PUID | User ID (for folder permissions) | None |
| PGID | Group ID (for folder permissions) | None |
| DEFAULT_TIMEZONE | Optional TZ name (e.g., America/Denver) for external ICS events; falls back to server local time |
None |
| SMTP_HOST | SMTP server hostname for sending emails (password reset, magic links) | None (email features disabled) |
| SMTP_PORT | SMTP server port | 587 |
| SMTP_USER | SMTP username/email for authentication | None |
| SMTP_PASSWORD | SMTP password or app-specific password | None |
| SMTP_USE_TLS | Use TLS for SMTP connection (true or false) |
true |
| SMTP_FROM_EMAIL | From address for sent emails | SMTP_USER value |
| SMTP_FROM_NAME | Display name for sent emails | DailyNotes |
| APP_URL | Public URL of your DailyNotes instance (used in email links) | http://localhost:8000 |
| Volume Name | Description |
|---|---|
| /app/config | Used to store DB and environment variables. This is not needed if you pass in all of the above environment variables. |
By default, the easiest way to get running is:
docker run -p 8000:8000 -v /config_dir:/app/config xhenxhe/dailynotesHere is a complete docker-compose example with all configuration options:
services:
dailynotes:
image: xhenxhe/dailynotes:latest
container_name: DailyNotes
ports:
- '8000:8000'
volumes:
# Persistent storage for database and config
- ./dailynotes-data:/app/config
environment:
# Required: Secret key for signing JWT tokens
# Generate with: openssl rand -hex 32
API_SECRET_KEY: 'your-secure-api-secret-key-here'
# Required: Encryption key for data at rest (must be multiple of 16)
# Generate with: openssl rand -hex 32
# WARNING: Changing this will make existing data unreadable!
DB_ENCRYPTION_KEY: 'your-secure-db-encryption-key-here'
# Optional: Database connection string
# Default: SQLite database in /app/config/app.db
# Examples:
# PostgreSQL: postgresql://user:password@host:5432/dailynotes
# MySQL: mysql+pymysql://user:password@host:3306/dailynotes?charset=utf8mb4
# DATABASE_URI: "sqlite:////app/config/app.db"
# Optional: Prevent new user signups (set to any value to disable)
# PREVENT_SIGNUPS: "true"
# Optional: Base URL when using a reverse proxy subfolder
# Example: If accessing via https://example.com/notes, set to "/notes"
# BASE_URL: ""
# Optional: Set user/group ID for file permissions
# Useful for matching host user permissions
# PUID: "1000"
# PGID: "1000"
restart: unless-stopped
# Optional: Health check
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:8000/health']
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Optional: Use with PostgreSQL
# postgres:
# image: postgres:15-alpine
# container_name: dailynotes-db
# environment:
# POSTGRES_DB: dailynotes
# POSTGRES_USER: dailynotes
# POSTGRES_PASSWORD: your-secure-db-password
# volumes:
# - ./postgres-data:/var/lib/postgresql/data
# restart: unless-stoppedQuick Start with Docker Compose:
-
Save the above as
docker-compose.yml -
Generate secure keys:
# Generate API secret key openssl rand -hex 32 # Generate DB encryption key openssl rand -hex 32
-
Update the
API_SECRET_KEYandDB_ENCRYPTION_KEYvalues -
Start the application:
docker-compose up -d
-
Access at
http://localhost:8000
Important Notes:
- The
DB_ENCRYPTION_KEYencrypts all your notes. Never change it after initial setup or your data will be unreadable! - The
./dailynotes-datadirectory will store your database and configuration - The default port is now
8000for better compatibility
If you're running DailyNotes behind a reverse proxy, here are example configurations for popular web servers.
Caddy
Caddy automatically handles HTTPS certificates. Add to your Caddyfile:
dailynotes.example.com {
reverse_proxy localhost:8000
}For a subfolder setup:
example.com {
handle_path /notes/* {
reverse_proxy localhost:8000
}
}Set BASE_URL=/notes in your DailyNotes environment when using a subfolder.
Nginx
server {
listen 80;
server_name dailynotes.example.com;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}For a subfolder setup:
location /notes/ {
proxy_pass http://localhost:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}Set BASE_URL=/notes in your DailyNotes environment when using a subfolder.
For HTTPS, add SSL configuration or use Certbot: sudo certbot --nginx -d dailynotes.example.com
Apache
Enable required modules:
sudo a2enmod proxy proxy_http headersVirtual host configuration:
<VirtualHost *:80>
ServerName dailynotes.example.com
ProxyPreserveHost On
ProxyPass / http://localhost:8000/
ProxyPassReverse / http://localhost:8000/
RequestHeader set X-Forwarded-Proto "http"
</VirtualHost>For a subfolder setup:
<Location /notes>
ProxyPass http://localhost:8000
ProxyPassReverse http://localhost:8000
RequestHeader set X-Forwarded-Proto "http"
</Location>Set BASE_URL=/notes in your DailyNotes environment when using a subfolder.
For HTTPS, use Certbot: sudo certbot --apache -d dailynotes.example.com
The easiest way to get started with development is using Docker. This approach provides:
- ✅ Consistent environment across all platforms (macOS, Linux, Windows)
- ✅ No need to install Python, Node.js, or manage versions
- ✅ Hot-reloading for both frontend and backend code
- ✅ Automatic database setup and migrations
- ✅ Isolated environment that won't affect your system
Quick Start:
# Start the development environment (builds, starts, and opens browser)
./devThat's it! The script will:
- Build the development Docker image
- Start both Quart backend (port 8000) and Vue.js (port 8080) servers
- Set up the database and run migrations
- Open your browser to http://localhost:8080
Development Workflow:
All your code changes will be automatically detected:
- Python/Quart changes: Uvicorn server auto-reloads
- Vue.js changes: Hot module replacement (HMR) updates the browser instantly
- Database: Persisted in
./config/app.dbon your host machine
Useful Commands:
# View live logs from both servers
docker compose -f docker-compose.dev.yml logs -f
# Stop the development environment
docker compose -f docker-compose.dev.yml down
# Restart services
docker compose -f docker-compose.dev.yml restart
# Access the container shell (for debugging)
docker exec -it dailynotes-dev bash
# Rebuild after dependency changes
docker compose -f docker-compose.dev.yml buildTesting with PostgreSQL or MySQL:
DailyNotes supports PostgreSQL and MySQL in addition to SQLite. To test with these databases:
# Start with PostgreSQL
./dev --postgres
# Start with MySQL
./dev --mysql
# Stop any database environment
./dev --downThese use production-like Docker Compose configurations (docker-compose-postgres.yml and docker-compose-mysql.yml) that include the database servers. The database data is persisted in Docker volumes.
File Structure:
Dockerfile.dev- Development Docker image with both Python and Node.jsdocker-compose.dev.yml- Development compose config with volume mountsdocker-compose-postgres.yml- PostgreSQL testing configurationdocker-compose-mysql.yml- MySQL testing configurationdocker-entrypoint-dev.sh- Startup script that runs both servers
What's Mounted:
The following directories are mounted from your host to enable hot-reloading:
./app/- Python backend code./client/src/- Vue.js source code./client/public/- Static assets./config/- Database and environment variables (persisted)./migrations/- Database migrations
Environment Variables:
By default, the development environment will auto-generate secure keys in ./config/.env. To customize:
# Edit docker-compose.dev.yml
environment:
API_SECRET_KEY: 'your-dev-key'
DB_ENCRYPTION_KEY: 'your-16-char-multiple-key'
PREVENT_SIGNUPS: 'true' # Optional: disable signupsIf you prefer to run services directly on your host machine:
The easiest way to set up your development environment is to use the automated setup script:
./dev-setup.shThis script will:
- Check for Python 3 and Node.js
- Automatically use Node.js 16 via nvm (if available)
- Create a Python virtual environment
- Install all Python dependencies
- Install all Node.js dependencies
- Generate environment configuration
After setup completes, run the development servers:
# Backend (in one terminal)
source venv/bin/activate
./run.sh
# Frontend (in another terminal)
cd client
npm run dev # Uses Node 16 via nvm automaticallyNote: The project includes a .nvmrc file that specifies Node.js 16. If you have nvm installed, it will automatically use the correct version.
If you're on Windows or prefer to set up manually:
You need Python 3.8+ and Node.js 16 installed.
Recommended: Use nvm (macOS/Linux) or nvm-windows to manage Node.js versions. The project includes a .nvmrc file that specifies Node.js 16.
# If using nvm (recommended)
nvm install 16
nvm use 16
# Create and activate virtual environment (recommended)
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
cd client
npm ciYou can use the environment variables from above, or you can generate new ones by running the following:
./verify_env.pyKeep in mind that since the data is encrypted, if you modify the DB_ENCRYPTION_KEY variable, your data will not be accessible anymore.
During development you need to run the client and server simultaneously
# Activate virtual environment first
source venv/bin/activate # On Windows: venv\Scripts\activate
# Run backend
./run.sh# In a separate terminal
cd client
# If using nvm, switch to Node 16 first (or run npm run dev)
nvm use # Reads from .nvmrc file
# Start frontend dev server
npm run serve
# Or use the dev script which automatically uses Node 16:
npm run dev