Sync your reMarkable 2 notes directly to Notion — no server, no cloud middleman.
rm2notion runs as a lightweight daemon directly on your reMarkable 2 tablet. Every 15 minutes it scans your whitelisted notebooks, OCRs any handwritten pages, and creates or updates tasks in a Notion database. One page = one Notion task.
- Runs on the tablet itself — pure Python, no external server required
- Per-page sync — each page in a notebook becomes its own Notion task
- Handwriting OCR via Google Cloud Vision (service account or API key)
- Typed text support — reads reMarkable's built-in text conversion output
- Custom page templates — Command Capture templates with TYPE, PRIORITY, and BIZ fields
- Smart marker detection — circle or check a TYPE/PRI field and it maps to your Notion schema
- AGENT routing — mark a page AGENT and it lands in your "Agent Inbox" queue automatically
- Survives firmware updates — installed to
/homepartition, which is never wiped - systemd daemon — starts on boot, restarts on failure
- reMarkable 2 tablet (firmware 3.x tested)
- SSH access enabled on the tablet (Settings → Help → Copyrights and licenses → Enable SSH at bottom)
- A Notion account with an internal integration token
- A Notion database configured with the expected schema (see below)
- (Optional) Google Cloud Vision API access for handwriting OCR
Go to Settings → Help → Copyrights and licenses, scroll to the bottom, and enable SSH. The password is shown on that screen.
Connect via USB (or WiFi) and run from your computer:
scp -r rm2notion/ root@10.11.99.1:/tmp/For WiFi, replace 10.11.99.1 with your tablet's IP (Settings → Help → Copyright and licenses shows it when SSH is enabled via WiFi).
ssh root@10.11.99.1
bash /tmp/rm2notion/setup.shThe setup script will:
- Install Python 3 via Entware (stored in
/home/opt_newso it survives firmware updates) - Copy rm2notion to
/opt/rm2notion/ - Install the systemd service
- Register the Command Capture page templates
- Create a starter config at
~/.config/rm2notion/config.json
Edit the config file on the tablet:
vi ~/.config/rm2notion/config.jsonFill in your Notion credentials:
{
"notion_api_key": "ntn_YOUR_INTEGRATION_TOKEN_HERE",
"notion_database_id": "YOUR_NOTION_DATABASE_ID",
"sync_notebooks": ["Your Notebook Name"],
"ocr_provider": "google_vision",
"google_vision_service_account": "/home/root/.config/rm2notion/google_vision_sa.json"
}python3 /opt/rm2notion/rm2notion.py syncsystemctl enable rm2notion
systemctl start rm2notion- Go to https://www.notion.so/my-integrations
- Click New integration, name it (e.g. "rm2notion"), select your workspace
- Copy the Internal Integration Token — this is your
notion_api_key
Create a Notion database (or use an existing one) and share it with your integration. The database must have these properties:
| Property name | Type | Notes |
|---|---|---|
Name |
Title | Page title |
Status |
Select | Backlog, In Progress, Done, etc. |
Priority |
Select | Low, Medium, High, Urgent, etc. |
Queue |
Select | Founder To-Do, Agent Inbox, etc. |
Task Type |
Select | General, Dev, Meeting, etc. |
Business |
Rich text | Free-text business/project context |
Source |
Select | reMarkable (auto-set) |
Synced |
Date | Auto-set on sync |
Content |
Rich text | Extracted note text |
To share your database with the integration: open the database in Notion → click ··· (top right) → Add connections → select your integration.
To find your database ID: open the database in Notion, look at the URL:
https://www.notion.so/yourworkspace/DATABASE_ID_HERE?v=...
Copy the 32-character ID (with hyphens it looks like xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
The included templates give you structured pages for different capture types. Use them in the tablet's Templates menu when creating a new page.
Portrait layout:
TITLE _______________________________________________
TYPE □ DAILY □ IDEA □ MEETING □ TASK ■ AGENT
PRI ○ LOW ○ MED ○ HIGH ○ URGENT BIZ ________
════════════════════════════════════════════════════
│ (lined writing area)
│
ACTION ITEMS □ ____________ □ ____________
Landscape layout: Same fields arranged horizontally for more writing space.
rm2notion scans OCR text from each page thumbnail for marker words:
- TYPE: Circle or check
DAILY,IDEA,MEETING,TASK→ sets Notion "Task Type" - PRI: Circle
LOW,MED,HIGH,URGENT→ sets Notion "Priority" - AGENT: Check the AGENT box → routes the task to your "Agent Inbox" queue
- BIZ: Any text written on the BIZ line → sets the "Business" field
Supports both a simple API key and a service account JSON (required if your project disables API key auth).
API key:
- Enable the Cloud Vision API
- Create an API key under APIs & Services → Credentials
- Set in config:
"ocr_provider": "google_vision","google_vision_api_key": "YOUR_KEY"
Service account (more secure):
- Create a service account in IAM & Admin → Service Accounts
- Grant it the Cloud Vision API User role
- Create and download a JSON key
- Copy the JSON to your tablet:
scp your-sa-key.json root@10.11.99.1:/home/root/.config/rm2notion/google_vision_sa.json - Set in config:
"ocr_provider": "google_vision","google_vision_service_account": "/home/root/.config/rm2notion/google_vision_sa.json"
The service account auth uses openssl (already on the tablet) to sign JWTs — no additional Python packages required.
If you use the tablet's built-in Convert → Text feature on a page, rm2notion will read that output first (before falling back to Vision OCR). Note: the built-in conversion only sees your handwritten ink layer, not the template background, so checkboxes and template labels won't appear in the output.
Set "ocr_provider": "none" to skip OCR entirely. Only typed text (Type Folio keyboard input) will be extracted.
Full list of ~/.config/rm2notion/config.json options:
{
"notion_api_key": "ntn_...",
"notion_database_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"sync_notebooks": ["Sync to notion"],
"sync_mode": "per_page",
"sync_interval_minutes": 15,
"ocr_provider": "google_vision",
"google_vision_api_key": "",
"google_vision_service_account": "/home/root/.config/rm2notion/google_vision_sa.json",
"myscript_app_key": "",
"myscript_hmac_key": "",
"default_status": "Backlog",
"default_priority": "Normal",
"default_queue_label": "Founder To-Do",
"default_task_type": "General",
"folder_task_type_map": {
"dev": "Dev",
"security": "Secure Coding",
"marketing": "Marketing",
"content": "Content",
"finance": "Finance"
},
"sync_folders": [],
"ignored_notebooks": [],
"log_level": "INFO"
}| Key | Description |
|---|---|
notion_api_key |
Internal integration token from notion.so/my-integrations |
notion_database_id |
ID of your target Notion database |
sync_notebooks |
List of notebook names to sync (case-sensitive). Empty = all notebooks |
sync_mode |
"per_page" (one task per page, recommended) or "per_notebook" |
sync_interval_minutes |
How often the daemon syncs (default: 15) |
ocr_provider |
"google_vision", "myscript", or "none" |
google_vision_service_account |
Path to service account JSON (preferred over API key) |
default_status |
Notion Status value for new tasks |
default_priority |
Notion Priority value when no PRI marker is detected |
default_queue_label |
Notion Queue value — use "Agent Inbox" for AGENT-marked pages |
folder_task_type_map |
Map reMarkable folder name keywords → Notion Task Type values |
ignored_notebooks |
Notebook names to always skip |
log_level |
"DEBUG", "INFO", "WARNING", or "ERROR" |
Run these on the tablet after SSHing in:
# One-time sync
python3 /opt/rm2notion/rm2notion.py sync
# Run as daemon (loops every sync_interval_minutes)
python3 /opt/rm2notion/rm2notion.py daemon
# List all notebooks on the device
python3 /opt/rm2notion/rm2notion.py list
# Show sync state (which pages have been synced)
python3 /opt/rm2notion/rm2notion.py status
# Show current config
python3 /opt/rm2notion/rm2notion.py config
# View live logs
journalctl -u rm2notion -f
# Restart the daemon after config changes
systemctl restart rm2notionreMarkable firmware updates wipe the root filesystem but leave /home untouched. rm2notion is installed to /home/opt_new/ (symlinked as /opt), so the Python environment and scripts survive. However, the symlink and systemd service need to be re-registered.
Run this once after any firmware update:
sh /home/root/reenable_rm2notion.shThis script rebuilds the /opt symlink, reinstalls the systemd service, and re-registers the page templates. It lives in /home/root/ so it also survives updates.
"No notebooks found matching sync_notebooks"
Check that the notebook name in config exactly matches the name shown on the tablet (case-sensitive). Run python3 /opt/rm2notion/rm2notion.py list to see exact names.
Handwriting not appearing in Notion
- Check that OCR is configured:
python3 /opt/rm2notion/rm2notion.py config - Check logs:
journalctl -u rm2notion -n 50 - Make sure the page thumbnail exists at
~/.local/share/remarkable/xochitl/{notebook_uuid}.thumbnails/{page_uuid}.png - Verify your Google Vision credentials have the Vision API enabled
SSH "REMOTE HOST IDENTIFICATION HAS CHANGED" This appears after a firmware update (the tablet generates a new host key). Fix it:
ssh-keygen -R 10.11.99.1
ssh-keygen -R 192.168.X.X # your tablet's WiFi IPTemplates not appearing on tablet After deploying new templates, restart xochitl:
systemctl restart xochitlService not starting
systemctl status rm2notion
journalctl -u rm2notion -n 100rm2notion/
├── rm2notion.py # Main sync script (runs on tablet)
├── setup.sh # Installer script (run on tablet via SSH)
├── reenable_rm2notion.sh # Post-firmware-update restore script
├── templates/
│ ├── command_capture_portrait.png # Template for portrait orientation
│ ├── command_capture_landscape.png # Template for landscape orientation
│ ├── command_capture_portrait.svg # Source SVG (editable)
│ └── command_capture_landscape.svg # Source SVG (editable)
└── .gitignore
MIT — do whatever you want with it.
Issues and PRs welcome. The main areas that could use improvement:
- Support for reMarkable's
.rmv6 stroke format for direct vector OCR (currently uses thumbnail PNG) - MyScript Nebo integration (API key auth already wired, needs testing)
- Multi-database routing (different notebooks → different Notion databases)
- reMarkable Cloud API as an alternative sync path (no SSH needed)
