Initial commit: Hermes Agent Skills collection

This commit is contained in:
Debian
2026-07-12 19:02:59 +00:00
commit e9cc106625
789 changed files with 233126 additions and 0 deletions
@@ -0,0 +1,64 @@
# Real-World Email Classification Examples
Actual inbox examples from production runs, showing what to classify and what to ignore.
## Order Confirmations → `Rechnungen_jjjj_mm/Bestellungen_jjjj_mm`
| Sender | Subject | Key Body Indicators |
|--------|---------|---------------------|
| `bestellbestaetigung@amazon.de` | "Bestellt: ..." | "Bestellnr.", "Vielen Dank für deine Bestellung" |
| `noreply@cateringexpert.de` | "Bestellung erfolgreich" | "Bestellbestätigung", "Verpflegungsteilnehmer" |
## Shipments/Notifications → IGNORE
| Sender | Subject | Why Ignore |
|--------|---------|------------|
| `shipment-tracking@amazon.de` | "In Zustellung: ..." | Delivery notification, not an order/invoice |
| `versandbestaetigung@amazon.de` | "Versendet: ..." | Shipping confirmation, not order/invoice |
| `order-update@amazon.de` | "zugestellt" / "geliefert" | Delivery complete |
| `rueckgabe@amazon.de` | Any | Return/refund, not an invoice |
| `noreply@dhl.de` | "Sendung liegt" / "Zustellung" | DHL delivery attempt notice |
## Invoices → `Rechnungen_jjjj_mm`
| Sender | Subject | Key Body Indicators |
|--------|---------|---------------------|
| `no-reply@amazon.de` | "Rechnung" / "Rechnung für" | "Rechnungsnummer", "Gesamtbetrag", "Umsatzsteuer" |
| `AmericanExpress@welcome.americanexpress.com` | "Online-Monatsabrechnung" | Credit card statement |
## Credit Memos → `Rechnungen_jjjj_mm/Gutschriften_jjjj_mm`
| Sender | Subject |
|--------|---------|
| Any | Subject contains "Gutschrift" |
## Typical Ignore Categories (never classify)
| Category | Senders | Examples |
|----------|---------|----------|
| **Marketing/Newsletter** | HelloFresh, Lidl, Rewe, Schuh-Schmid, Bestsecret, Metro, mymuesli, aldi-sued | Deal alerts, weekly offers |
| **Social/Job** | `mailrobot@mail.xing.com`, `jobalerts-noreply@linkedin.com`, Facebook, Pinterest | Birthdays, job alerts, connections |
| **Solar/Technical** | `noreply@sunnyportal.com` | Tagesertrag reports, error reports |
| **Shipping Delays** | `noreply@dhl.de` | "verspätet sich" delivery delay notices |
| **Marketing/Newsletter** | HelloFresh, Lidl, Rewe, Schuh-Schmid, Bestsecret, Metro, mymuesli, aldi-sued | Deal alerts, weekly offers |
| **Energy/Utility Promo** | `news@email.eis.de` | "Guthaben verfällt" balance expiry reminders — marketing, not invoice |
| **Payment App Marketing** | `noreply@hello.klarna.com` | "Bleib flexibel" / "Bezahl mit Klarna" — app feature promo, not invoice |
| **Newsletters** | `newsletter@news.reolinksupport.com`, `newsletter@deals.banggood.com` | Product news, deals |
| **System** | `noreply-dmarc-support@google.com` | DMARC reports |
| **Personal** | `ich@dominikschoen.de`, `debug1@grafiniert.de` | Non-business emails |
## Decision Flow
```
1. Does subject contain "Rechnung" or "Gutschrift"?
→ Yes: read body for Rechnung# / Gesamtbetrag → invoice OR gut-schrift
→ No: continue
2. Does subject contain "bestellt" / "bestellung"?
→ Yes: read body
- Contains "Bestellnr." + "Vielen Dank" → order confirmation
- Contains "In Zustellung" / "zugestellt" / "versendet" → shipment (ignore)
- Contains "Rücksendung" / "Erstattung" → return (ignore)
3. Otherwise: IGNORE
```
@@ -0,0 +1,101 @@
# Discovered Email Patterns — 2026-06
## Invoices (Rechnungen)
### IONITY — Lade-Rechnung
- **Sender**: `no-reply@ionity.eu`
- **Subject**: "Danke, dass du bei IONITY geladen hast"
- **Body**: Contains "Die aktuelle Rechnung ist im Anhang"
- **Attachment**: PDF invoice
- **Folder**: `Rechnungen_YYYY_MM`
### ASFINAG — Österreichische Maut-Rechnung
- **Sender**: `shop@asfinag.at`
- **Subject**: "Abbuchung Digitale Streckenmaut FLEX"
- **Body**: Contains "Rechnungsnummer", "wurde die Abbuchung ... durchgeführt"
- **Attachment**: PDF invoice
- **Folder**: `Rechnungen_YYYY_MM`
### GitHub — Payment Receipt (Sponsoring)
- **Sender**: `noreply@github.com`
- **Subject**: "[GitHub] Payment Receipt for ..."
- **Body**: Contains "GITHUB RECEIPT", "Sponsorship Amount", "Total:"
- **Attachment**: PDF receipt
- **Note**: Steuerlich oft nicht relevant, aber dokumentationswert
- **Folder**: `Rechnungen_YYYY_MM`
## Order Confirmations (Bestellungen)
*None discovered in 2026-06 scan.*
## Common Ignores (non-invoices/non-orders)
### noris network — Incident Notifications
- **Sender**: `support@noris.de`
- **Subject**: "Incident resolved (Prio X): [...]" or "Störung Internetuplink"
- **Body**: Contains "Ticketnummer", "Status: Resolved", "DDS"
- **Reason**: IT incident report, not a financial document
### Cateringexpert — System Migrations
- **Sender**: `noreply@cateringexpert.de`
- **Subject**: "Migration erfolgreich abgeschlossen", "Caterer Wechsel", "Konto Migration"
- **Body**: Contains "Umstellung", "Migration", "storniert wurden"
- **Reason**: System notification, not a real order or invoice
- **Note**: Distinguish from actual catering orders (subject: "Bestellung erfolgreich")
### Temu — Product Recommendations (not orders)
- **Sender**: via Apple Private Relay (e.g., `temu_at_eu_temuemail_com_xxx@privaterelay.appleid.com`)
- **Subject**: "Ihre Bestellung wird von uns übernommen!" or "Passend zu dem, was Sie gekauft haben"
- **Body**: Product recommendations, no order details, no order number
- **Reason**: Marketing disguised as order confirmation
- **Note**: Real Temu order confirmations contain "Bestellnummer" and item details
### Sunny Portal — Solar Energy Reports
- **Sender**: `noreply@sunnyportal.com`
- **Subject**: "Sunny Portal Info Report", "Ereignis Report"
- **Body**: Contains "Tagesertrag", "kWh", "CO2 Minderung", "Fehler"
- **Reason**: Technical energy report, not an invoice
- **Already in learning.json ignore_patterns**
### DKB Finanz-News
- **Sender**: `kundeninformation@emails.dkb.de`
- **Subject**: "Das ist neu im Juni", "DKB Finanz-News"
- **Reason**: Bank newsletter, not a statement or invoice
## New Patterns — 2026-06-09 Session
### Temu — Fake Invoice Subject (Marketing Disguised as Invoice)
- **Sender**: via Apple Private Relay (`temu_at_eu_temuemail_com_xxx@privaterelay.appleid.com`)
- **Subject**: "Deine Rechnung wird übernommen!"
- **Body**: Marketing content, product recommendations, no actual invoice or order details
- **Reason**: Temu uses deceptive subject lines ("Rechnung", "Bestellung", "Lieferung") to drive engagement — never an actual invoice or order confirmation
- **Note**: Real Temu order confirmations contain a verifiable Bestellnummer and itemized list with prices
### Oura Ring — Order Confirmation
- **Sender**: `orders@ouraring.com`
- **Subject**: "Your order is being processed" / "Your sizing kit is being processed"
- **Body**: Contains "Order number" (SO-XXXXXXX), "Sale date", "Order Total", itemized with price and VAT
- **Note**: This is a **bestellbestätigung** (order confirmation), NOT an invoice. The actual invoice/receipt comes later via email. The Sizing Kit (€5.95, discounted to €0) is also an order confirmation for the sizing tool.
- **Folder**: `Rechnungen_YYYY_MM/Bestellungen_YYYY_MM`
### American Express — Monthly Statement
- **Sender**: `AmericanExpress@welcome.americanexpress.com`
- **Subject**: "Deine Online-Monatsabrechnung liegt bereit"
- **Body**: Contains "Kontonummer-Endung", "Monatsabrechnung", "SEPA-Lastschrift", link to online card account
- **Reason**: Credit card monthly statement — financial document worth archiving even though it's not a traditional invoice from a vendor
- **Folder**: `Rechnungen_YYYY_MM`
## False Positives — Service Responses with Invoice Keywords
### AEG/Elxtra — Service Response (NOT an invoice)
- **Sender**: `service@aeg.de`, `service@elxtra.com`
- **Subject**: Ticket reply like "AW: Akkulaufzeit nicht mehr akzeptabel. Bitte um Ersatz Ticket-Nr.: 5a50166#3282150"
- **Body**: Contains "rechnung", "umsatzsteuer", "bestellung" (from embedded shop page HTML in the link)
- **Classification**: IGNORE — customer support response with spare parts shop link, not an invoice
- **Pitfall**: Body-level keyword search for invoice terms is misleading here. Always check if the email is a service/support response.
### ALH Dokumentenservice — Insurance Portal Notification (NOT an invoice)
- **Sender**: `Dokumentenservice@alte-leipziger.de`
- **Subject**: "Erinnerung an ein neues Dokument zu Ihrem ALH-Vertrag" or "Neue Dokumente zu Ihrem ALH-Vertrag"
- **Body**: Mentions "Leistungsabrechnungen" but only as UI navigation instruction ("In Hallesche4u finden Sie Leistungsabrechnungen...")
- **Classification**: IGNORE — insurance portal document notification, not a financial document
- **Pitfall**: "Abrechnungen" in body is UI text, not an actual billing document.
@@ -0,0 +1,89 @@
# Learning JSON File Formats
Two formats exist in production. The **structured** format is newer and preferred.
## Format A — Flat (older, in `continuous-invoice-organizer` learning.json)
```json
{
"corrections": [],
"patterns": {
"rechnung": {
"amazon_rechnung": {
"sender": "no-reply@amazon.de",
"subject_keywords": ["rechnung", "rechnung für"],
"content_keywords": ["rechnungsnummer", "rechnungsdatum", "rechnungsempfänger", "umsatzsteuer-id", "zahlbetrag"]
}
},
"bestellbestaetigung": {
"amazon_bestellung": {
"sender": "bestellbestaetigung@amazon.de",
"subject_keywords": ["bestellt"],
"content_keywords": ["bestellnr", "vielen dank fuer deine bestellung"]
}
},
"ignorieren": {
"amazon_lieferung": {
"sender": "order-update@amazon.de",
"subject_keywords": ["zugestellt", "geliefert"]
}
}
},
"last_scan": "2026-05-19",
"total_processed": 150,
"total_moved": 5
}
```
**Used by:** `continuous-invoice-organizer` cronjob (German-language prompts)
**Keys:** `rechnung`, `bestellbestaetigung`, `ignorieren` (German)
## Format B — Structured (newer, in `continuous-email-organization` cronjob prompt)
```json
{
"corrections": [],
"patterns": {
"invoices": {
"american_express": {
"sender": "AmericanExpress@welcome.americanexpress.com",
"subject_pattern": "Online-Monatsabrechnung",
"folder": "Rechnungen_2026_05"
}
},
"orders": {
"amazon_de": {
"sender_pattern": "bestellbestaetigung@amazon.de",
"subject_pattern": "Bestellt:",
"folder": "Rechnungen_2026_05/Bestellungen_2026_05"
}
},
"ignore_patterns": {
"shipment_tracking": "shipment-tracking@amazon.de + In Zustellung → ignoriert"
},
"structured_ignore_patterns": [
{
"name": "amazon_versand",
"sender": "versandbestaetigung@amazon.de",
"subject_keywords": ["versendet", "versand"],
"notes": "Amazon Versandbestätigung"
}
]
},
"last_scan": "2026-05-19",
"total_processed": 150,
"total_moved": 5
}
```
**Used by:** `continuous-email-organization` cronjob prompt template
**Keys:** `invoices`, `orders`, `ignore_patterns`, `structured_ignore_patterns`
## Migration note
When merging patterns from both systems:
- `rechnung` (Format A) → maps to `invoices` (Format B)
- `bestellbestaetigung` (Format A) → maps to `orders` (Format B)
- `ignorieren` (Format A) → maps to `structured_ignore_patterns` (Format B)
Always check which format the existing file uses before writing updates.
@@ -0,0 +1,27 @@
# Discovered Patterns — 2026-06-16 Session
## False Positive — AEG Service Response
- **Sender**: `service@aeg.de`
- **Subject**: "AW: Akkulaufzeit nicht mehr akzeptabel. Bitte um Ersatz Ticket-Nr.: 5a50166#3282150"
- **Body**: Contains keywords "rechnung", "umsatzsteuer", "bestellung" (likely in legal footer or shop page HTML) — but is a **service response** pointing to a web shop link for spare parts. No actual invoice attached.
- **Classification**: IGNORE (service response, not a financial document)
- **Pitfall**: Body-level keyword search for invoice terms is misleading here. Always check if the email is a service/customer support response.
## False Positive — ALH Dokumentenservice Notifications
- **Sender**: `Dokumentenservice@alte-leipziger.de`
- **Subjects**: "Erinnerung an ein neues Dokument zu Ihrem ALH-Vertrag", "Neue Dokumente zu Ihrem ALH-Vertrag"
- **Body**: Mentions "Leistungsabrechnungen" (in the context of "In Hallesche4u finden Sie Leistungsabrechnungen Ihrer Krankenversicherung") but is just a **notification** that new documents are available in the ALH insurance portal.
- **Classification**: IGNORE (insurance portal notification, not an invoice)
- **Pitfall**: The word "Abrechnungen" in the body is part of UI navigation instructions, not an actual billing document.
## Microsoft Teams Notifications
- **Sender**: `no-reply@teams.mail.microsoft`
- **Subject**: "[Name] hat eine Nachricht gesendet."
- **Body**: Contains HTML with inline images, Teams chat content
- **Classification**: IGNORE (chat notification)
## DKB Referral Marketing
- **Sender**: `kundeninformation@emails.dkb.de`
- **Subject**: "170 € Sommer-Aktion ☀️"
- **Body**: "Letzte Chance: Girokonto empfehlen & mehr verdienen" — referral campaign
- **Classification**: IGNORE (marketing, not a statement or invoice)
@@ -0,0 +1,27 @@
# Session Notes — 2026-06-12
## Key Discovery: DHL Delivery Keyword Variants
The existing DHL ignore pattern used `"zustellung"` / `"sendung liegt"` as keywords, but this session revealed **two additional phrasings** that are NOT caught by those patterns:
| Pattern | Example Subject | Catch Rate |
|---|---|---|
| Old: `zustellung` / `sendung liegt` | `Ihre ARO SUPERMERCHANT Sendung liegt am gewünschten Ablageort` | ❌ Missed |
| New: `liegt am Ablageort` | `Ihre ARO SUPERMERCHANT Sendung liegt am gewünschten Ablageort` | ✅ |
| New: `wird gleich zugestellt` | `Ihre ARO SUPERMERCHANT Sendung wird gleich zugestellt` | ✅ |
**Action taken in learning.json:**
- Added structured ignore pattern `dhl_paket_lieferung` with keywords `["liegt am Ablageort", "wird gleich zugestellt"]` for sender `noreply@dhl.de`
- This runs alongside (not replacing) the existing DHL `zustellung` pattern — both are needed for full coverage.
## Secondary Discovery: Oura Delivery Notification
| Pattern | Example Subject | Type |
|---|---|---|
| `Die Lieferung deiner Bestellung ist für morgen geplant!` | orders@ouraring.com | Delivery notification (ignore) |
| `Great news: Your Oura Ring shipped!` | orders@ouraring.com | Shipment notification (ignore) |
| `Your order is being processed` | orders@ouraring.com | Order confirmation (archive) |
**Action taken:** Added structured ignore pattern `oura_delivery` for sender `orders@ouraring.com` with keywords `["lieferung", "shipped", "delivery"]`.
**Important distinction:** Same sender, different intent. "Order is being processed" → order confirmation. "Delivery" / "shipped" → ignore. Body reading is critical for disambiguation.
@@ -0,0 +1,53 @@
# Amazon Email Classification Patterns
Patterns for distinguishing Amazon invoice emails from order confirmations and delivery notifications.
## Key Distinctions
### Invoice (Rechnung)
- **Sender:** `no-reply@amazon.de` (Amazon forwards Marketplace invoices)
- **Subject:** `[Wichtig] Rechnung für Ihre Bestellung`
- **Body keywords:** `Rechnungsnummer`, `Rechnungsdatum`, `Zahlbetrag`, `Umsatzsteuer`, `USt-IdNr`, `Rechnungsempfänger`
- **Note:** Always check the `From` address — Amazon Marketplace sellers (Hugendubel, etc.) send invoices via Amazon's forwarding system. The actual seller name appears in the body.
### Order Confirmation (Bestellbestätigung)
- **Sender:** `bestellbestaetigung@amazon.de`
- **Subject:** `Bestellt: „[Artikelname]..."`
- **Body keywords:** `Bestellnr.`, `Vielen Dank für deine Bestellung`, `Summe` (but NOT `Rechnungsnummer`)
- **Note:** This is NOT an invoice. The invoice arrives separately after delivery.
### Shipment Notification (versendet/in Zustellung)
- **Sender:** `order-update@amazon.de`
- **Subject:** Contains `versendet` or `In Zustellung`
- **Body keywords:** `In Zustellung`, `Versendet`
- **Action:** Skip — not an invoice or order confirmation
### Delivery Confirmation (Zugestellt/Geliefert)
- **Sender:** `order-update@amazon.de`
- **Subject:** `Zugestellt: „[Artikelname]..."` or `Geliefert: „[Artikelname]..."`
- **Body keywords:** `zugestellt`, `Die Sendung wurde im Garten hinterlegt`
- **Action:** Skip — delivery notification, not an invoice
### DHL Delivery Notification
- **Sender:** `noreply@dhl.de`
- **Subject:** Contains `Sendung liegt am gewünschten Ablageort` or similar
- **Action:** Skip — courier notification, not an invoice
## Quick Decision Tree
```
From bestellbestaetigung@amazon.de → Bestellbestätigung
From no-reply@amazon.de + "Rechnung" in subject → Invoice (read body for seller)
From order-update@amazon.de → Check subject:
"Zugestellt" / "Geliefert" → Delivery → skip
"versendet" / "In Zustellung" → Shipment → skip
From noreply@dhl.de → Delivery notification → skip
From any other seller directly → Check body for "Rechnungsnummer" → Invoice
```
## Session Examples
| ID | Sender | Subject | Type | Target |
|----|--------|---------|------|--------|
| 48474 | no-reply@amazon.de (Hugendubel) | [Wichtig] Rechnung für Ihre Bestellung | Rechnung | Rechnungen_2026_05 |
| 48502 | bestellbestaetigung@amazon.de | Bestellt: „Kulturen Komplex..." | Bestellbestätigung | Bestellungen_2026_05 |
@@ -0,0 +1,62 @@
# Cron Job Model Fix (Post-Update)
**Session:** 2026-05-08 | **Impact:** All cron jobs with `model: null` break after Hermes Agent update
## Problem
After a Hermes Agent update, cron jobs with `model: null` fail immediately:
```
Error code: 400 - {'message': 'model should be in provider/model format'}
```
The new codepath strictly validates the `model` field — it no longer auto-resolves from config.
## Fix Pattern
### Step 1: Identify affected jobs
```bash
hermes cron list
```
Look for jobs where `model: null`.
### Step 2: Apply explicit model
```bash
hermes cron update <job-id> --model '{"model": "<provider>/<model>"}'
```
Example for this setup:
```bash
hermes cron update <job-id> --model '{"model": "custom/llama/model.gguf"}'
```
### Step 3: Verify
```bash
hermes cron list --name <job-name>
```
Confirm `model` is now `custom/llama/model.gguf`.
### Step 4: Restart gateway (if running)
```bash
systemctl --user restart hermes-gateway
```
The gateway process caches job definitions in memory. Restart ensures it loads updated models.
## Prevention
When creating new cron jobs, **always** specify the model explicitly:
```bash
hermes cron create --name "..." --model '{"model": "custom/llama/model.gguf"}' --prompt "..."
```
Or for script-only jobs (no LLM needed), set `--no-agent`:
```bash
hermes cron create --name "..." --script "..." --no-agent
```
## Session-Specifics (2026-05-08)
Three jobs were affected by the update to v0.13.0 (commit 474d1e812):
- `SRE Network Reconnaissance` (4733a436d99a) → fixed
- `Rechnungen-Organizer` (f773f8c23230) → fixed
- `memory-sync-daily` (ce496a8d1062) → deleted and recreated as `no_agent: true` script-only
@@ -0,0 +1,70 @@
# learning.json Format
The learning file at `~/.hermes/email-organizer/learning.json` accumulates email classification rules across cron runs.
## Structure
```json
{
"corrections": [],
"patterns": {
"rechnung": {
"<pattern_name>": {
"sender": "exact@sender.com",
"sender_pattern": "@appleid.com",
"subject_keywords": ["keyword1", "keyword2"],
"content_keywords": ["rechnung", "rechnungsnummer"],
"folder": "Rechnungen_YYYY_MM",
"source": "Vendor name",
"notes": "Description and context"
}
},
"bestellbestaetigung": {
"<pattern_name>": { ... }
},
"ignorieren": {
"<pattern_name>": { ... }
}
},
"structured_ignore_patterns": [],
"last_scan": "2026-06-10 09:13",
"total_processed": 554,
"total_moved": 20
}
```
## Pattern Fields
| Field | Required | Description |
|-------|----------|-------------|
| `sender` | Conditional | Exact email address (use with `sender_pattern` only) |
| `sender_pattern` | Conditional | Email domain pattern with `@` prefix (e.g., `@appleid.com`) |
| `subject_keywords` | Yes | Keywords matched case-insensitively in subject |
| `content_keywords` | Conditional | Keywords matched in email body |
| `folder` | Conditional | Override target folder (defaults based on category) |
| `source` | Yes | Human-readable vendor/source name |
| `notes` | Yes | Context: what type of email, whether it's an invoice or order, any caveats |
## Decision Criteria
### Invoice (Rechnung)
Contains a **factored charge**: Rechnungsnummer, Gesamtbetrag, Steuern, Fälligkeitsdatum, Lastschrift.
### Order Confirmation (Bestellbestätigung)
Contains an **order number and items** but NO invoice details. Invoice arrives separately.
### Ignore
- Delivery notifications ("zugestellt", "versendet")
- Newsletters, marketing emails
- Social media notifications
- Job alerts
- Technical reports (solar, DMARC)
## Update Workflow
When a new pattern emerges during a scan:
1. Add entry under the correct category in `patterns`
2. Include `source` with vendor name
3. Include `notes` with distinguishing characteristics
4. Increment `total_processed` and `total_moved`
5. Update `last_scan` timestamp
@@ -0,0 +1,214 @@
# Himalaya Configuration Reference
Configuration file location: `~/.config/himalaya/config.toml`
## Minimal IMAP + SMTP Setup
```toml
[accounts.default]
email = "user@example.com"
display-name = "Your Name"
default = true
# IMAP backend for reading emails
backend.type = "imap"
backend.host = "imap.example.com"
backend.port = 993
backend.encryption.type = "tls"
backend.login = "user@example.com"
backend.auth.type = "password"
backend.auth.raw = "your-password"
# SMTP backend for sending emails
message.send.backend.type = "smtp"
message.send.backend.host = "smtp.example.com"
message.send.backend.port = 587
message.send.backend.encryption.type = "start-tls"
message.send.backend.login = "user@example.com"
message.send.backend.auth.type = "password"
message.send.backend.auth.raw = "your-password"
```
## Password Options
### Raw password (testing only, not recommended)
```toml
backend.auth.raw = "your-password"
```
### Password from command (recommended)
```toml
backend.auth.cmd = "pass show email/imap"
# backend.auth.cmd = "security find-generic-password -a user@example.com -s imap -w"
```
### System keyring (requires keyring feature)
```toml
backend.auth.keyring = "imap-example"
```
Then run `himalaya account configure <account>` to store the password.
## Gmail Configuration
```toml
[accounts.gmail]
email = "you@gmail.com"
display-name = "Your Name"
default = true
backend.type = "imap"
backend.host = "imap.gmail.com"
backend.port = 993
backend.encryption.type = "tls"
backend.login = "you@gmail.com"
backend.auth.type = "password"
backend.auth.cmd = "pass show google/app-password"
message.send.backend.type = "smtp"
message.send.backend.host = "smtp.gmail.com"
message.send.backend.port = 587
message.send.backend.encryption.type = "start-tls"
message.send.backend.login = "you@gmail.com"
message.send.backend.auth.type = "password"
message.send.backend.auth.cmd = "pass show google/app-password"
```
**Note:** Gmail requires an App Password if 2FA is enabled.
## iCloud Configuration
```toml
[accounts.icloud]
email = "you@icloud.com"
display-name = "Your Name"
backend.type = "imap"
backend.host = "imap.mail.me.com"
backend.port = 993
backend.encryption.type = "tls"
backend.login = "you@icloud.com"
backend.auth.type = "password"
backend.auth.cmd = "pass show icloud/app-password"
message.send.backend.type = "smtp"
message.send.backend.host = "smtp.mail.me.com"
message.send.backend.port = 587
message.send.backend.encryption.type = "start-tls"
message.send.backend.login = "you@icloud.com"
message.send.backend.auth.type = "password"
message.send.backend.auth.cmd = "pass show icloud/app-password"
```
**Note:** Generate an app-specific password at appleid.apple.com
### iCloud + Custom Domains (iCloud+)
When using a custom domain with iCloud+, the IMAP login username is **often your primary iCloud username** (the part before `@icloud.com`), NOT the custom domain email address. The SMTP username needs the **full email address** including the custom domain.
This is the #1 reason custom domain iCloud setups fail — clients auto-detect "iCloud" and try `full@customdomain.com` as the IMAP login, which Apple's IMAP server rejects.
```toml
[accounts.icloud-custom]
email = "name@deinedomain.de"
display-name = "Your Name"
backend.type = "imap"
backend.host = "imap.mail.me.com"
backend.port = 993
backend.encryption.type = "tls"
backend.login = "deinprimär" # <-- PRIMARY iCloud username ONLY (no @icloud.com)
backend.auth.type = "password"
backend.auth.cmd = "pass show icloud/app-password"
message.send.backend.type = "smtp"
message.send.backend.host = "smtp.mail.me.com"
message.send.backend.port = 587
message.send.backend.encryption.type = "start-tls"
message.send.login = "name@deinedomain.de" # <-- FULL custom domain email for SMTP
message.send.backend.auth.type = "password"
message.send.backend.auth.cmd = "pass show icloud/app-password"
```
**Troubleshooting:** Apple docs say the IMAP username is "usually the name of your iCloud Mail email address (for example, johnappleseed, not johnappleseed@icloud.com)". If the bare username doesn't work, fall back to the full email address.
## Folder Aliases
Map custom folder names:
```toml
[accounts.default.folder.alias]
inbox = "INBOX"
sent = "Sent"
drafts = "Drafts"
trash = "Trash"
```
## Multiple Accounts
```toml
[accounts.personal]
email = "personal@example.com"
default = true
# ... backend config ...
[accounts.work]
email = "work@company.com"
# ... backend config ...
```
Switch accounts with `--account`:
```bash
himalaya --account work envelope list
```
## Notmuch Backend (local mail)
```toml
[accounts.local]
email = "user@example.com"
backend.type = "notmuch"
backend.db-path = "~/.mail/.notmuch"
```
## OAuth2 Authentication (for providers that support it)
```toml
backend.auth.type = "oauth2"
backend.auth.client-id = "your-client-id"
backend.auth.client-secret.cmd = "pass show oauth/client-secret"
backend.auth.access-token.cmd = "pass show oauth/access-token"
backend.auth.refresh-token.cmd = "pass show oauth/refresh-token"
backend.auth.auth-url = "https://provider.com/oauth/authorize"
backend.auth.token-url = "https://provider.com/oauth/token"
```
## Additional Options
### Signature
```toml
[accounts.default]
signature = "Best regards,\nYour Name"
signature-delim = "-- \n"
```
### Downloads directory
```toml
[accounts.default]
downloads-dir = "~/Downloads/himalaya"
```
### Editor for composing
Set via environment variable:
```bash
export EDITOR="vim"
```
@@ -0,0 +1,199 @@
# Message Composition with MML (MIME Meta Language)
Himalaya uses MML for composing emails. MML is a simple XML-based syntax that compiles to MIME messages.
## Basic Message Structure
An email message is a list of **headers** followed by a **body**, separated by a blank line:
```
From: sender@example.com
To: recipient@example.com
Subject: Hello World
This is the message body.
```
## Headers
Common headers:
- `From`: Sender address
- `To`: Primary recipient(s)
- `Cc`: Carbon copy recipients
- `Bcc`: Blind carbon copy recipients
- `Subject`: Message subject
- `Reply-To`: Address for replies (if different from From)
- `In-Reply-To`: Message ID being replied to
### Address Formats
```
To: user@example.com
To: John Doe <john@example.com>
To: "John Doe" <john@example.com>
To: user1@example.com, user2@example.com, "Jane" <jane@example.com>
```
## Plain Text Body
Simple plain text email:
```
From: alice@localhost
To: bob@localhost
Subject: Plain Text Example
Hello, this is a plain text email.
No special formatting needed.
Best,
Alice
```
## MML for Rich Emails
### Multipart Messages
Alternative text/html parts:
```
From: alice@localhost
To: bob@localhost
Subject: Multipart Example
<#multipart type=alternative>
This is the plain text version.
<#part type=text/html>
<html><body><h1>This is the HTML version</h1></body></html>
<#/multipart>
```
### Attachments
Attach a file:
```
From: alice@localhost
To: bob@localhost
Subject: With Attachment
Here is the document you requested.
<#part filename=/path/to/document.pdf><#/part>
```
Attachment with custom name:
```
<#part filename=/path/to/file.pdf name=report.pdf><#/part>
```
Multiple attachments:
```
<#part filename=/path/to/doc1.pdf><#/part>
<#part filename=/path/to/doc2.pdf><#/part>
```
### Inline Images
Embed an image inline:
```
From: alice@localhost
To: bob@localhost
Subject: Inline Image
<#multipart type=related>
<#part type=text/html>
<html><body>
<p>Check out this image:</p>
<img src="cid:image1">
</body></html>
<#part disposition=inline id=image1 filename=/path/to/image.png><#/part>
<#/multipart>
```
### Mixed Content (Text + Attachments)
```
From: alice@localhost
To: bob@localhost
Subject: Mixed Content
<#multipart type=mixed>
<#part type=text/plain>
Please find the attached files.
Best,
Alice
<#part filename=/path/to/file1.pdf><#/part>
<#part filename=/path/to/file2.zip><#/part>
<#/multipart>
```
## MML Tag Reference
### `<#multipart>`
Groups multiple parts together.
- `type=alternative`: Different representations of same content
- `type=mixed`: Independent parts (text + attachments)
- `type=related`: Parts that reference each other (HTML + images)
### `<#part>`
Defines a message part.
- `type=<mime-type>`: Content type (e.g., `text/html`, `application/pdf`)
- `filename=<path>`: File to attach
- `name=<name>`: Display name for attachment
- `disposition=inline`: Display inline instead of as attachment
- `id=<cid>`: Content ID for referencing in HTML
## Composing from CLI
### Interactive compose
Opens your `$EDITOR`:
```bash
himalaya message write
```
### Reply (opens editor with quoted message)
```bash
himalaya message reply 42
himalaya message reply 42 --all # reply-all
```
### Forward
```bash
himalaya message forward 42
```
### Send from stdin
```bash
cat message.txt | himalaya template send
```
### Prefill headers from CLI
```bash
himalaya message write \
-H "To:recipient@example.com" \
-H "Subject:Quick Message" \
"Message body here"
```
## Tips
- The editor opens with a template; fill in headers and body.
- Save and exit the editor to send; exit without saving to cancel.
- MML parts are compiled to proper MIME when sending.
- Use `himalaya message export --full` to inspect the raw MIME structure of received emails.
@@ -0,0 +1,64 @@
# `message read` Behavior Notes
## TUI vs Subprocess — Context-Dependent
**Skill states** that `message read` hangs because it uses an interactive TUI. However, in practice (June 2026, cron job context):
```bash
# This DID work and returned body content:
himalaya message read 49830 --folder INBOX
# Output: ~3000 chars of decoded email body
# This also worked:
himalaya message read 49826 --folder INBOX
# Output: ~1100 chars
```
**When `message read` works from subprocess:**
- Terminal-like environment (not truly headless — `TERM`/`STY`/`TMUX` may be set)
- Email bodies are small-to-medium (< 10KB)
- No special characters that trigger interactive prompts
**When `message read` will likely fail:**
- True headless environment with no TTY allocation
- Large emails (> 20KB) that trigger pagination
- Emails with HTML attachments that trigger rendering prompts
**Safe default:** If `message read` works, use it. If it returns nothing or hangs, fall back to `message export --full` (see himalaya skill).
## `message export --full` as Fallback
When `message read` fails:
```python
result = subprocess.run(
["himalaya", "message", "export", msg_id, "--full"],
capture_output=True, text=True,
encoding="latin-1", timeout=30
)
```
Output is raw MIME — parse headers by splitting on `\n\n`, then decode MIME-encoded headers with `email.header.decode_header()`.
## Email Classification Patterns (Order vs Invoice)
| Signal | Likely Type |
|--------|-------------|
| Subject: "Bestellung erfolgreich", "Bestellung wurde bestätigt", "Your order is being processed" | Bestellbestätigung |
| Subject: "Deine Bestellung wird bald eintreffen", "Order coming soon", "Wir packen Deine Bestellung" | Bestellbenachrichtigung (delivery, not invoice) |
| Contains Bestellnummer/Order number + Artikel/Items + Preis but NO Rechnungsnummer/Gesamtbetrag/Steuern | Bestellbestätigung → `Bestellungen_jjjj_mm` |
| Contains Rechnungsnummer, Gesamtbetrag, Steuern, Fälligkeitsdatum | Rechnung → `Rechnungen_jjjj_mm` |
| Subject: "20 % Rabatt", "Sonderangebot", "Aktion" with discount code in body | Marketing/Newsletter → ignore |
| Subject: "Sendung kommt heute", "Live verfolgen" with DHL/Paketdienst | Versandtracking → ignore |
| Subject: "Bestellung hat einen lokalen Boost erhalten" with product recommendations | Marketing → ignore |
| Subject: "Ihre Sendung kommt heute" with DHL/Paketdienst | Versandtracking → ignore |
## `folder create` vs `message move` for Nested Folders
In practice (June 2026 cron session):
- `himalaya folder create "Rechnungen_2026_06"` returned exit code 1 with empty stderr — appeared to silently fail or succeed
- `himalaya folder create "Rechnungen_2026_06/Bestellungen_2026_06"` same behavior
- `himalaya folder list` showed the folders existed after `message move` — suggesting `message move` auto-created them
- Safe pattern: always use `message move "Parent/Child" <ID>` for nested paths, and check `folder list` to confirm
## `imap_codec` Warnings
During `message move`, `WARN imap_codec::response: Rectified missing text to "..."` may appear between the command and the success message. These are harmless IMAP protocol quirks. The actual output ends with `Message(s) successfully moved from INBOX to FolderName!`.