# imap-commands — Formation Recipe (FvW v8 §38)

**Module ID:** `imap-commands`  
**Module name:** Imap Operations Facade  
**Lifecycle phase:** formation  
**Status:** INTENT ONLY. No code yet.  
**Date:** 2026-07-21  

---

## 1. Intent (the "why")
- Solve FreshCloud's core challenge: **unified IMAP interaction** for multi-user, multi-account email systems  
- Eliminate AGPLv3 legacy by providing a clean-room MIT implementation of IMAP operations  
- Enable **offline-first architecture** with encrypted caching  
- Provide **audit trail** for compliance and security  
- Simplify UI development through abstraction over complex IMAP protocol  

---

## 2. User-facing behaviour
- **Folder Operations**: Create/delete/rename mailboxes, subscribe/unsubscribe folders  
- **Message Handling**: Fetch messages, mark as read/unread, move/copy/delete messages  
- **Metadata Management**: Retrieve folder statistics, message lists, UID validity  
- **ACL Control**: Manage shared mailbox permissions  
- **Quota Monitoring**: Check mailbox storage limits  
- **High-Level UX Helpers**: `markAsRead(msgId)`, `moveToFolder(msgId, folder)`, `deleteMessage(msgId)`  

---

## 3. Public contract (data shapes + signatures)
```php
namespace FreshCloud\Mail\ImapOperations;

class ImapCommands {
    // Core operations
    public function fetchMessage(string $folder, string $uid): MessageStream;
    public function fetchMessageList(string $folder, int $page, int $pageSize): MessageList;
    public function appendMessage(string $folder, string $rawMessage): bool;
    public function markMessageAsRead(string $uid, bool $read): void;
    public function moveMessage(string $uid, string $sourceFolder, string $targetFolder): bool;
    public function deleteMessage(string $uid, string $folder): bool;
    
    // Advanced features
    public function createFolder(string $name): bool;
    public function deleteFolder(string $name): bool;
    public function setAcl(string $folder, string $user, string $rights): bool;
    public function getQuota(string $folder = ''): QuotaInfo;
    public function getFolderList(): FolderList;
}
```

### Key data shapes
```typescript
interface MessageStream {
  headers: Record<string, string>;
  body: ReadableStream;
  attachments: Attachment[];
  size: number;
}

interface MessageList {
  messages: Array<{
    uid: string;
    flags: string[];
    size: number;
    date: Date;
    from: string;
    subject: string;
  }>;
  total: number;
  nextPage: number | null;
}

interface QuotaInfo {
  used: number;
  total: number;
  percent: number;
}
```

---

## 4. Persistence (what gets stored where)
- **Audit Logs**: Written to PocketBase `email_audit` table (columns: user_id, account_id, command, params, result, duration_ms, timestamp)  
- **Cache**: Local filesystem storage encrypted with user-derived key (`/cache/{user_id}/{account_id}/{folder}/meta.json`)  
- **No new database tables**: Reuse existing `email_audit`, `email_drafts`  

---

## 5. Dependencies (what this module uses, what uses this)
| Module/Type | Direction | Purpose |
|-------------|----------|---------|
| `imap-client` | Uses | Raw IMAP connection and protocol handling |
| `auth` | Uses | User authentication context |
| `crypto` | Uses | Cache encryption |
| `config` | Uses | Server settings |
| `pocketbase` | Uses | Audit log storage |
| `email-drafts` | Used by | Appending draft messages |
| `ui-react` | Used by | High-level mailbox operations |

---

## 6. Behaviour inventory (the N public methods)
| Method | Signature | Purpose | Side Effects | Errors |
|--------|-----------|---------|--------------|--------|
| `fetchMessage` | (string $folder, string $uid): MessageStream | Retrieve full message | Cache update, audit log | ProtocolError, InvalidUid |
| `fetchMessageList` | (string $folder, int $page, int $pageSize): MessageList | Paginated message list | Cache update, audit log | FolderNotFound, QuotaExceeded |
| `appendMessage` | (string $folder, string $rawMessage): bool | Store new message | Audit log, cache invalidation | MessageSizeExceeded, WritePermission |
| `markMessageAsRead` | (string $uid, bool $read): void | Update message flag | Audit log, cache update | MessageNotFound, InvalidFlag |
| `moveMessage` | (string $uid, string $sourceFolder, string $targetFolder): bool | Transfer message | Audit log, cache invalidation | FolderNotFound, WritePermission |
| `createFolder` | (string $name): bool | New mailbox creation | Audit log | InvalidFolderName, AlreadyExists |
| `setAcl` | (string $folder, string $user, string $rights): bool | Grant permissions | Audit log | AccessDenied, InvalidRights |

---

## 7. State machine
```mermaid
graph TD
  A[Connection Ready] --> B{Command Executed}
  B -->|Success| C[Update Cache]
  B -->|Failure| D[Handle Error]
  C --> E[Emit Audit Event]
  D --> F[Log Error]
  E --> G[Return Result]
  F --> H[Throw Exception]
  G --> A
  H --> A
```

**States**:  
1. `CONNECTION_READY`: Valid IMAP session exists  
2. `CACHE_UPDATED`: Offline cache refreshed  
3. `AUDIT_EMITTED`: Log entry recorded  

**Transitions**:  
- Command execution triggers state updates  
- Errors transition to error handling state  

---

## 8. Side effects
| Effect | Trigger | Target |
|--------|---------|--------|
| Cache write | fetchMessage/fetchMessageList | `/cache/{user}/{account}/{folder}/meta.json` |
| Cache invalidation | moveMessage/deleteMessage | Affected folder cache |
| Audit log write | All commands | PocketBase `email_audit` |
| Event emission | Sieve scripts | `sieve_execution` event |
| Counter increment | IMAP command duration | `imap_command_duration_seconds` |

---

## 9. Input validation
- **Folder Names**: Must match `^[^"/[\]]+$` per RFC 3501 §5.1  
- **Message UIDs**: Must be integers between 1 and 4,294,967,295  
- **ACL Rights**: Must be valid `lrswipcda` combinations  
- **Raw Message**: Must have valid MIME structure (Content-Type headers)  
- **Page Size**: 1-100 messages per request  

---

## 10. Failure modes
| Failure | Handling | Caller Result |
|---------|----------|---------------|
| NetworkTimeout | Retry 3x with exponential backoff | RuntimeException after retries |
| FolderNotFound | Log error, throw exception | Exception with error code |
| QuotaExceeded | Reject operation, emit alert | QuotaExceeded exception |
| CacheCorrupted | Regenerate cache, warn admin | DataCorruption warning |
| ProtocolError | Close connection, reset state | ConnectionClosed exception |

---

## 11. User simulation
**Scenario 1: Mark message as read**  
- Given User has message UID 1234 in Inbox  
- When They click "Mark as read"  
- Then Message flag is updated on server and cache  
- And Audit log records the operation  

**Scenario 2: Move message between folders**  
- Given User selects message from "Drafts"  
- When They choose "Move to Sent"  
- Then Message appears in Sent folder  
- And Drafts folder cache is invalidated  

**Scenario 3: Offline cache usage**  
- Given Network is disconnected  
- When User requests message list  
- Then Offline cache data is returned  
- And Warning shown: "Using cached data"  

**Scenario 4: ACL permission error**  
- Given User lacks write permission on folder  
- When They try to delete message  
- Then Operation fails with AccessDenied  
- And Audit logs the violation  

---

## 12. Reproducibility test
1. Create fresh PHP 8.0+ project with composer  
2. Install `freshcloud/mail-imap-client:1.0`  
3. Configure test IMAP server credentials (Greenmail)  
4. Create user with account in PocketBase  
5. Fetch message list for INBOX folder  
6. Append test message using sample.eml  
7. Move message to "Sent" folder  
8. Verify audit log entry in PB  
9. Confirm cache directory created with encrypted files  

---

## 13. Substitutability (FvW v8 §37.4)
**Drop-in replacement requirements**:  
1. Same public interface with identical method signatures  
2. Identical error handling (exception types and codes)  
3. Cache key structure: `{user_id}/{account_id}/{folder}`  
4. Audit log schema in `email_audit` table  
5. Support offline cache without network calls  
6. UID validity matching mechanism  
7. Command timeout behavior (30s default)  

---

## 14. Invariants (the 5 things that must always hold)
1. **User Isolation**: All operations scoped to `user_id/account_id`  
2. **Cache Encryption**: Local cache files AES-256 encrypted  
3. **Audit Trail**: Every command creates PB record  
4. **No Secrets**: Module never stores credentials  
5. **Stateless Operations**: No persistent state between calls  

---

## 15. Sweep fields applied (the 14 design intent fields)

### Sweep 1: Multi-user + PocketBase
- **user_scoping**: Methods validate `user_id/account_id` from ImapClient  
- **pb_integration**: Audit logs written to `email_audit` table  
- **auth_boundary**: Inherits auth from ImapClient (no local auth)  
- **state_isolation**: Per-user cache dirs (`/{user_id}/{account_id}/`)  
- **logout_behaviour**: Cleanup via ImapClient pool destruction  
- **multi_account**: Each command tagged with `account_id` for audit  

### Sweep 2: World-class Abilities
- **connection_pool**: Uses existing pooled ImapClient (no new pools)  
- **offline_cache**: `fetchMessageList` checks cache before network call  
- **sieve_integration**: RunScript method emits `sieve_execution` event  
- **audit_logging**: Every command writes to `email_audit`  
- **ux_primitives**: `markAsRead`, `moveToFolder` simplify UI interaction  

### Sweep 3: Migration + Observability + Tests
- **migration**: Works with SnappyMail-stored UIDs (no UID mapping)  
- **observability**: Metrics `imap_command_duration_seconds`, `imap_commands_total`  
- **tests**: PHPUnit tests with Greenmail mock server (included in deps)  

---

## 16. Cross-references
- FvW v8 §11: Constitutional implementation rules  
- FvW v8 §37: Substitutability requirements  
- FvW v8 §38: Formation intent documentation  
- FvW v8 §19: Clean-room reimplementation reference  
- `recipes/engine/log/formation.md`: Pattern for similar modules  
- `email-drafts`: Uses `appendMessage` for sending drafts  

---

## 17. What we are intentionally NOT doing (deferred to future)
1. Full MIME parsing (separate `mime-parser` module)  
2. Server quota management (read-only quotas)  
3. Sieve script editing (only execution)  
4. Message threading support  
5. Multi-threaded command batching  
6. IMAP IDLE push notifications  
7. Custom header fetching (only predefined sets)  
8. UTF-8 folder name conversion (client handles)  
9. Partial message fetch (always full message)  
10. ACL wildcard support (explicit user targets only)  
11. IMAP COMPRESS extension  
12. UID mapping for server migrations  
13. Maildir format support (IMAP-only)  
14. Message search filtering (UI layer responsibility)  

*Difference from MailSo*: MailSo had 7 PHP files (ACL, Folders, Messages, Metadata, Quota, UrlAuth, Main). We reduce to 4 files (ImapCommands facade + internal classes) for simplicity and AGPL avoidance.