# mime-content — Formation Recipe (FvW v8 §38)

**Module ID:** `mime-content`
**Module name:** Mime Content
**Lifecycle phase:** formation
**Status:** INTENT ONLY. No code yet.
**Date:** 2026-07-21

---

## 1. Intent (the "why")
We need a clean-room, AGPL-free representation of MIME structures for email. This module solves the problem of safely handling complex email messages with multi-part content, attachments, and encoding while being user-context-aware. It acts as the canonical "email shape" for FreshCloud's data plane, ensuring all email operations (compose, send, receive) operate on validated, structured data without cross-contamination between user accounts or risk from AGPLv3 lineage.

---

## 2. User-facing behaviour
- **Compose:** Create new emails with headers, body, and attachments
- **Send:** Serialize messages to RFC-compliant MIME format
- **Receive:** Parse raw RFC822 messages into structured components
- **Preview:** Extract display-friendly metadata (sender name, preview text, localized date)
- **Validate:** Verify email addresses and attachment constraints
- **Cache:** Serialize/deserialize message objects for offline use

---

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

interface MessageInterface {
  public function __construct(
    string $subject,
    EmailAddress $sender,
    EmailCollection $recipients
  );
  
  public function addAttachment(AttachmentInterface $attachment): void;
  public function setBodyText(string $text): void;
  public function setBodyHtml(string $html): void;
  public function serialize(): string; // RFC-compliant MIME string
  public function getPreviewText(): string; // First 200 chars of body_text
  public function getDisplayDate(): string; // User's localized timezone
  public function getSenderName(): string; // Display name or email fallback
  public function getRecipients(): EmailCollection;
}

interface AttachmentInterface {
  public function getFilename(): string;
  public function getContentType(): string;
  public function getSize(): int;
  public function getContent(): string; // Base64-encoded
}

interface EmailAddress {
  public static function fromString(string $email): self;
  public function isValid(): bool;
  public function getDisplayName(): ?string;
  public function getEmail(): string;
}

interface EmailCollection {
  public function add(EmailAddress $email): void;
  public function toArray(): array;
  public function getUniqueRecipients(): array;
}

class ParameterCollection implements \IteratorAggregate {
  public function addParameter(string $name, string $value): void;
  public function hasParameter(string $name): bool;
}

class EmailAuditEvent {
  public function __construct(
    string $user_id,
    string $action, // 'create', 'attach', 'send'
    string $message_id
  );
}
```

---

## 4. Persistence (what gets stored where)
- **PocketBase Tables:**
  - `email_drafts` 
    - Stores serialized message objects (MIMEContent\Message) 
    - Columns: `id`, `user_id`, `message_id`, `draft_data`, `created_at`
  - `email_audit`
    - Records: Module actions with context
    - Columns: `id`, `user_id`, `module`, `action`, `message_id`, `timestamp`
- **Local Cache:**
  - Serialized message objects stored in `user_data/mime-cache/{message_id}`
  - Encrypted with user-derived key from PB session
- **In-memory State:**
  - Only during active email editing/sending

---

## 5. Dependencies (what this module uses, what uses this)
- **Uses:**
  - `mime-enums` (constants: MIME_TYPES, MAX_ATTACHMENT_SIZE)
  - `log` (audit logging)
  - `crypto` (for local cache encryption)
- **Used by:**
  - `mail-sender` (for message serialization)
  - `mail-receiver` (for parsing incoming emails)
  - `email-editor` (UI bridge for drafting)
  - `draft-manager` (persistence layer)

---

## 6. Behaviour inventory (the N public methods)
| Method                | Signature                                                                 | Purpose                            | Side Effects               | Errors                          |
|-----------------------|--------------------------------------------------------------------------|------------------------------------|----------------------------|---------------------------------|
| `serialize()`         | `(): string`                                                             | Output RFC-compliant MIME string   | None                       | `SerializationError`            |
| `getPreviewText()`    | `(): string`                                                             | First 200 chars of body text       | None                       | `NoBodyException`               |
| `getDisplayDate()`    | `(): string`                                                             | User-localized timestamp          | None                       | None                            |
| `getSenderName()`     | `(): string`                                                             | Display name or email fallback     | None                       | `NoSenderException`             |
| `addAttachment()`     | `(AttachmentInterface): void`                                            | Attach file to message             | Audit event                | `AttachmentTooLarge`, `InvalidType` |
| `setBodyText()`       | `(string): void`                                                         | Set plaintext body                | MIME content-type update    | `EncodingError`                 |
| `setBodyHtml()`       | `(string): void`                                                         | Set HTML body                     | MIME content-type update    | `EncodingError`                 |
| `fromRfc822()`       | `(string): static`                                                       | Parse raw email string             | None                       | `ParseError`                    |

---

## 7. State machine
```
[New] --(set subject/sender)--> [Draft] 
  --(add attachments/set body)--> [Ready] 
    --(serialize)--> [Sent] 
      --(cached)--> [Archived]

[Rfc822] --(parse)--> [Parsed] --(cache)--> [Cached]
```

- **Transitions:**
  - `Draft → Ready`: When minimum required fields (subject, sender) exist
  - `Ready → Sent`: After successful serialization for sending
  - `Parsed → Cached`: When saving to local cache
  - `Parsed → Archived`: When marked as read in UI

---

## 8. Side effects
- **Writes:**
  - `email_audit` table: Writes audit entries on message actions
  - `email_drafts` table: On draft save
  - Local cache: `user_data/mime-cache/{message_id}` 
- **Reads:**
  - Session: Current user context (`user_id`)
  - `email_drafts` table: On draft load
  - Filesystem: Attachment content during composition
- **Emits:**
  - Audit events: `mime_content_create`, `mime_content_attach`, `mime_content_serialize`
  - Metrics: `mime_content_operations_total`, `mime_content_attachments_size_total`

---

## 9. Input validation
- Email addresses must conform to RFC5322 (via `Email::fromString()`)
- Attachment files:
  - Size ≤ `MAX_ATTACHMENT_SIZE` (from mime-enums)
  - Content type in allowed MIME types (e.g., image/*, text/plain)
- Subject line max 998 characters (RFC2047)
- Message body max 25MB (configurable per org)
- Duplicate recipients allowed but deduplicated in `getUniqueRecipients()`

---

## 10. Failure modes
| Failure               | Handling Strategy                                 | Caller Sees                     |
|-----------------------|--------------------------------------------------|---------------------------------|
| `ParseError`          | Throw with line number of malformed data         | `MimeParseException` exception   |
| `AttachmentTooLarge`  | Reject attachment, log to audit                  | `ValidationException`           |
| `InvalidType`         | Reject MIME type, log audit event               | `ValidationException`           |
| `CacheWriteFailure`   | Retry 3 times, then fail gracefully              | `CacheException`                |
| `SerializationError`  | Return empty MIME string, log audit event         | `SerializationException`        |

---

## 11. User simulation
1. **Compose New Email**
   - Given: User opens email composer
   - When: Adds recipient, subject, text body, attachment
   - Then: MIME structure created in memory

2. **Send Email**
   - Given: Valid message in "Ready" state
   - When: User clicks "Send"
   - Then: Serialized MIME passed to mail-sender

3. **View Sent Email**
   - Given: Received RFC822 email
   - When: User opens message
   - Then: Parsed into Message object, sender name extracted

4. **Save Draft**
   - Given: In-progress email
   - When: User clicks "Save Draft"
   - Then: Serialized to `email_drafts` table

5. **Load Draft**
   - Given: Draft exists in `email_drafts`
   - When: User opens draft
   - Then: Deserialized into Message object

6. **Large Attachment Rejection**
   - Given: User tries to attach 10MB file
   - When: Attachment size exceeds 5MB limit
   - Then: Rejected with error, audit log created

---

## 12. Reproducibility test
1. Create `MimeContent` namespace in `src/`
2. Implement `MessageInterface` with required methods
3. Create `Attachment` class with size/content validation
4. Add PocketBase integration for `email_drafts` writes
5. Implement cache serialization with user encryption
6. Set up audit logging for module actions
7. Write unit tests for serialization roundtrip
8. Validate email parsing with Greenmail
9. Confirm all methods return correct UTF-8 data

---

## 13. Substitutability (FvW v8 §37.4)
An alternative implementation must provide:
- Same public interface (`MessageInterface`, `AttachmentInterface`)
- Identical state machine transitions
- Same audit event emissions
- Same cache serialization format
- Equivalent email address validation
- No AGPLv3-licensed code

---

## 14. Invariants (the 5 things that must always hold)
1. All email addresses validated before use
2. Attachment metadata always stored with MIME boundaries
3. User content never written to unencrypted cache
4. Audit events contain traceable `user_id`
5. Serialized MIME always starts with `MIME-Version: 1.0`

---

## 15. Sweep fields applied (the 14 design intent fields)
### Sweep 1: Multi-user + PocketBase (6 fields)
- **user_scoping**  
  *Design:* All operations scoped by `$user_id` from session  
  *Implementation:* Every method accepts `UserContext` parameter
- **pb_integration**  
  *Design:* Drafts persisted via `email_drafts` table  
  *Implementation:* `saveDraft()` writes to PB, `loadDraft()` reads from PB
- **auth_boundary**  
  *Design:* No auth in module, relies on caller  
  *Implementation:* Assumes valid user context passed by `session-engine`
- **state_isolation**  
  *Design:* No shared state between users  
  *Implementation:* All data structures instance-scoped
- **logout_behaviour**  
  *Design:* User data immediately garbage collected  
  *Implementation:* No persistent state, all in-memory
- **multi_account**  
  *Design:* Zero account context awareness  
  *Implementation:* Methods ignore account differentiation

### Sweep 2: World-class Abilities (5 fields)
- **connection_pool**  
  *Design:* Not applicable (in-memory only)  
  *Implementation:* No HTTP connections in module
- **offline_cache**  
  *Design:* Messages serialized for offline access  
  *Implementation:* `serializeToCache()` creates encrypted files
- **sieve_integration**  
  *Design:* Not applicable (no rules engine)  
  *Implementation:* No Sieve-specific methods
- **audit_logging**  
  *Design:* All actions audited for security  
  *Implementation:* Audit events emitted on state changes
- **ux_primitives**  
  *Design:* Canonical "email" object for UI  
  *Implementation:* `getPreviewText()`, `getSenderName()`, `getDisplayDate()` methods

### Sweep 3: Migration + Observability + Tests (3 fields)
- **migration**  
  *Design:* No DB schema changes needed  
  *Implementation:* Uses existing PB tables
- **observability**  
  *Design:* Metrics on MIME operations  
  *Implementation:* `mimeTypeOperations_total` counter
- **tests**  
  *Design:* PHPUnit for all critical paths  
  *Implementation:* Tests for parse/serialize roundtrip, validation, audit events

---

## 16. Cross-references
- §11: Lifecycle governance  
- §19: MailSo as historical reference  
- §37: Constitutional fragments  
- §38: Formation phase doctrine  
- Dependencies: `mime-enums` (§12), `log` (§13)  
- Dependent modules: `mail-sender` (§8), `mail-receiver` (§9)

---

## 17. What we are intentionally NOT doing (deferred to future)
1. **IMAP/SMTP protocols** (handled by `mail-sender`/`mail-receiver`)
2. **HTML/CSS rendering** (UI layer responsibility)
3. **Sieve filtering** (deferred to future `rules-engine`)
4. **Message threading** (deferred to `conversation-engine`)
5. **DKIM signing** (handled by `mail-sender`)
6. **PGP encryption** (deferred to `security-engine`)
7. **Calendar/event parsing** (deferred to `ical-engine`)
8. **MailSo's complex folder structure** (simplified to two states: draft/sent)
9. **Message-ID regeneration** (handled by `mail-sender`)
10. **MIME boundary generation** (internal, not exposed)
11. **Content-transfer encoding auto-selection** (simplified to base64/binary)
12. **Quoted-printable encoding** (deferred to `mail-sender`)
13. **Advanced charset detection** (UTF-8 only)
14. **Partial message downloading** (handled by `mail-receiver`)
15. **Message status tracking** (deferred to `mail-receiver`)