Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,27 @@ All configuration is controlled via environment variables in the `docker run` co
| `CUSTOM_SEARCH_URL_FIELD` | _(empty)_ | Custom search URL field |
| `CUSTOM_SEARCH_DESCRIPTION_FIELD` | _(empty)_ | Custom search description field |

**Optional — Email:**

| Variable | Default | Description |
| ------------------------------------- | --------- | ----------------------------------- |
| `EMAIL_GMAIL_CLIENT_ID` | _(empty)_ | Gmail OAuth2 client ID |
| `EMAIL_GMAIL_CLIENT_SECRET` | _(empty)_ | Gmail OAuth2 client secret |
| `EMAIL_GMAIL_REFRESH_TOKEN` | _(empty)_ | Gmail OAuth2 refresh token |
| `EMAIL_GMAIL_ACCESS_TOKEN` | _(empty)_ | Gmail OAuth2 access token (optional) |
| `EMAIL_GRAPH_CLIENT_ID` | _(empty)_ | MS Graph OAuth2 client ID |
| `EMAIL_GRAPH_CLIENT_SECRET` | _(empty)_ | MS Graph OAuth2 client secret |
| `EMAIL_GRAPH_REFRESH_TOKEN` | _(empty)_ | MS Graph OAuth2 refresh token |
| `EMAIL_GRAPH_TENANT_ID` | _(empty)_ | MS Graph Azure AD tenant ID |
| `EMAIL_GRAPH_ACCESS_TOKEN` | _(empty)_ | MS Graph OAuth2 access token (optional) |
| `EMAIL_IMAP_HOST` | `imap.gmail.com` | IMAP server hostname |
| `EMAIL_IMAP_PORT` | `993` | IMAP server port |
| `EMAIL_IMAP_USER` | _(empty)_ | IMAP username |
| `EMAIL_IMAP_PASSWORD` | _(empty)_ | IMAP password / app password |
| `EMAIL_IMAP_SECURE` | `true` | Use SSL/TLS for IMAP connection |
| `EMAIL_SMTP_HOST` | _(same as IMAP)_ | SMTP server hostname for sending |
| `EMAIL_SMTP_PORT` | `587` | SMTP server port (STARTTLS) |

**Optional — Sandbox:**

| Variable | Default | Description |
Expand Down Expand Up @@ -472,6 +493,7 @@ All built-in tools are defined in `src/tools/` and registered as LangChain tools
| `visionAnalyze` | Analyze images via OpenAI multimodal LLM. Accepts URL or base64 data URI. |
| `webExtract` | Extract readable text content from a web page URL. Supports summarization for large pages. |
| `webSearch` | Search the web via DuckDuckGo, Google, Bing, SearXNG, or Custom endpoints. |
| `email` | Read, send, manage drafts, organize, and search emails. Actions: `read`, `send`, `draftSave`, `draftList`, `draftUpdate`, `draftDelete`, `organize`, `search`. Requires email provider credentials via environment variables. |

**Deep Agents tools:** Core filesystem operations (`readFile`, `writeFile`, `patch`, `searchFiles`) and task management (`todo`) are provided by [deepagentsjs](https://github.com/langchain-ai/deepagentsjs) and are not listed as madz-built-in tools.

Expand Down
17 changes: 17 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ providers:
maxTokens: 4096
rateLimit:
requestsPerMinute: 120
email:
provider:
type: gmail
userId: me
defaultFolder: INBOX
maxAttachments: 10
maxAttachmentSize: 25mb
sandbox:
paths:
- "./"
Expand All @@ -31,6 +38,16 @@ sandbox:
- NODE_ENV
- OPENAI_API_KEY
- AUTH_API_KEY
- EMAIL_IMAP_HOST
- EMAIL_IMAP_PORT
- EMAIL_IMAP_SECURE
- EMAIL_IMAP_USER
- EMAIL_IMAP_PASSWORD
- EMAIL_SMTP_HOST
- EMAIL_SMTP_PORT
- EMAIL_DEFAULT_FOLDER
- EMAIL_MAX_ATTACHMENTS
- EMAIL_MAX_ATTACHMENT_SIZE
permissions:
- filesystem:read
- filesystem:write
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-08-16
69 changes: 69 additions & 0 deletions openspec/changes/archive/2026-08-16-email-integration/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
## Context

Madz currently has no email integration. The tool system (`src/tools/index.js`) uses a tiered permission model with factory-pattern tool registration. Configuration is loaded from `config.yaml` via Zod schemas. External dependencies follow the existing pattern of installing npm packages and wiring them into the tool factory.

## Goals / Non-Goals

**Goals:**
- Implement provider-abstracted email tools (read, send, draft, organize)
- Support Gmail, MS Graph, and IMAP providers
- Register tools in the tier2 tool map with `network:outbound` permission
- Add provider configuration schemas validated at startup
- Store OAuth tokens securely in the memory system

**Non-Goals:**
- Push/webhook email notifications (pull-only v1)
- Multi-account support (single provider per session)
- Calendar integration (separate issue #780)
- Email encryption/signing (PGP/S/MIME)
- Mobile push notifications

## Decisions

**Decision 1: Provider abstraction via adapter pattern**
- Use a common `EmailProvider` interface with methods: `send()`, `read()`, `search()`, `draft()`, `organize()`
- Each provider (Gmail, Graph, IMAP) implements this interface
- Rationale: Allows adding new providers without modifying existing tool code; mirrors patterns already used in the codebase

**Decision 2: IMAP as universal fallback**
- When no OAuth provider is configured, IMAP provides access to any email service
- IMAP uses username/password stored in encrypted form
- Rationale: Universal compatibility without requiring OAuth setup; Gmail and Graph offer richer features but require OAuth

**Decision 3: Single provider per session**
- Configuration supports one active provider at a time
- Simplifies authentication state management and token lifecycle
- Rationale: Multi-account support adds significant complexity; single provider covers 95% of use cases

**Decision 4: OAuth2 tokens stored in memory system**
- OAuth tokens persisted via the existing memory writer/reader system
- Tokens encrypted at rest, decrypted on use
- Rationale: Reuses existing secure storage pattern; avoids creating a new credentials module

**Decision 5: Attachments reuse web.js validation**
- Attachment MIME validation and path resolution reuse patterns from `src/tools/web.js`
- Rationale: DRY principle; attachment handling is identical regardless of email provider

**Decision 6: Dependencies — googleapis + nodemailer**
- Gmail: `googleapis` (official Google API client)
- MS Graph: `@microsoft/microsoft-graph-client` (official Microsoft client)
- IMAP: `nodemailer` (supports IMAP transport, single dependency)
- Rationale: Official clients have best OAuth support; nodemailer covers IMAP without adding a second IMAP library

## Risks / Trade-offs

[Risk: OAuth token refresh complexity] → Mitigation: Use official SDKs' built-in token refresh; implement retry logic with exponential backoff
[Risk: IMAP credential security] → Mitigation: Encrypt credentials at rest using existing memory encryption; never log credentials
[Risk: Large attachment handling] → Mitigation: Stream large attachments; enforce size limits via config
[Risk: Provider API rate limits] → Mitigation: Implement client-side rate limiting; respect API headers
[Risk: Dependency bloat] → Mitigation: Only install providers that are configured; lazy-load provider modules

## Migration Plan

No migration needed — this is a new feature with no existing email code. The tools are opt-in: they only activate when email provider credentials are configured in `config.yaml`.

## Open Questions

1. Should draft auto-save be configurable (interval)?
2. What is the maximum attachment size limit?
3. Should email tools support thread/conversation grouping?
43 changes: 43 additions & 0 deletions openspec/changes/archive/2026-08-16-email-integration/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
## Why

Email is the central nervous system of professional workflows, yet Madz has zero capability to interact with email systems. Users cannot ask the agent to triage their inbox, draft replies, organize email threads, or manage calendar scheduling via email. This is a critical gap for any agent designed to assist with professional workflows.

## What Changes

- Add `email.read` tool — fetch messages from inbox, sent, drafts, or custom folders with filtering
- Add `email.send` tool — compose and send emails with text/HTML body, attachments, CC/BCC
- Add `email.draft` tools — save, list, update, and delete email drafts
- Add `email.organize` tool — label/archive messages, mark as read/unread, search
- Add multi-provider support: Gmail (Gmail API), Outlook/MS Graph (Microsoft Graph API), IMAP (fallback)
- Add provider configuration schemas for OAuth2 and IMAP credentials
- Register email tools in the tool map with appropriate permissions

## Capabilities

### New Capabilities
- `email-tools`: Core email tool interface with read, send, draft, and organize operations
- `email-providers`: Multi-provider abstraction layer (Gmail, MS Graph, IMAP)
- `email-auth`: Authentication and credential management for email providers

### Modified Capabilities
- `tools-tier2`: New tools registered in the tier2 tool map
- `tool-classification`: New email tool classifications added
- `config-system`: Email provider configuration schemas added

## Impact

- `src/tools/index.js` — New email tools registered with TOOL_PERMISSIONS and TOOL_CLASSIFICATIONS
- `src/config/loader.js` — Email provider config loading and validation
- `src/config/schemas/providers.js` — New email provider configuration schemas
- `src/tools/web.js` — Attachment validation patterns reused
- `src/agent/deepAgents.js` — Email tools available to agents via tool map
- New dependencies: `googleapis`, `@microsoft/microsoft-graph-client`, `nodemailer` or `imap-simple`
- New files: `src/tools/email.js`, `src/tools/email/providers/base.js`, `src/tools/email/providers/gmail.js`, `src/tools/email/providers/graph.js`, `src/tools/email/providers/imap.js`

## Non-goals

- Push/webhook-based email notification (pull-only for v1)
- Multi-account support (single provider per session)
- Calendar integration (cross-referenced issue #780, separate feature)
- Email encryption/signing (PGP/S/MIME)
- Mobile push notifications
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
## ADDED Requirements

### Requirement: OAuth2 credential storage
The system SHALL store OAuth2 access and refresh tokens in the memory system with encryption at rest.

#### Scenario: Store OAuth2 credentials securely
- **WHEN** OAuth2 tokens are obtained from a provider
- **THEN** they are encrypted and stored in the memory system under a provider-specific key

#### Scenario: Retrieve OAuth2 credentials
- **WHEN** a provider needs its tokens
- **THEN** the system decrypts and returns the stored tokens

#### Scenario: Rotate OAuth2 credentials
- **WHEN** a new refresh token is obtained during token refresh
- **THEN** the system updates the stored credentials atomically

#### Scenario: Clear OAuth2 credentials on logout
- **WHEN** the provider is disconnected or credentials are invalidated
- **THEN** the system removes the stored tokens from the memory system

### Requirement: IMAP credential storage
The system SHALL store IMAP credentials (host, port, username, password) in the memory system with encryption at rest.

#### Scenario: Store IMAP credentials securely
- **WHEN** IMAP credentials are configured
- **THEN** they are encrypted and stored in the memory system under a provider-specific key

#### Scenario: Retrieve IMAP credentials
- **WHEN** the IMAP provider needs credentials
- **THEN** the system decrypts and returns the stored credentials

#### Scenario: Never log IMAP credentials
- **WHEN** any operation involving IMAP credentials
- **THEN** credentials are never written to logs, error messages, or telemetry data

### Requirement: Credential validation at startup
The system SHALL validate email provider credentials during application startup.

#### Scenario: Validate Gmail OAuth2 credentials on startup
- **WHEN** the application starts with Gmail provider configured
- **THEN** it validates the OAuth2 credentials by making a test API request

#### Scenario: Validate IMAP credentials on startup
- **WHEN** the application starts with IMAP provider configured
- **THEN** it validates the credentials by attempting an IMAP connection

#### Scenario: Graceful degradation when credentials are invalid
- **WHEN** the application starts with invalid email credentials
- **THEN** it logs a warning and continues without email tools, rather than crashing

### Requirement: Credential configuration schema
The system SHALL define Zod validation schemas for email provider configurations.

#### Scenario: Validate Gmail provider config
- **WHEN** a Gmail provider config is provided
- **THEN** the schema validates clientId, clientSecret, refreshToken, and required scopes

#### Scenario: Validate MS Graph provider config
- **WHEN** an MS Graph provider config is provided
- **THEN** the schema validates clientId, clientSecret, refreshToken, tenantId, and required scopes

#### Scenario: Validate IMAP provider config
- **WHEN** an IMAP provider config is provided
- **THEN** the schema validates host, port, username, and password fields

#### Scenario: Reject incomplete provider config
- **WHEN** a provider config is missing required fields
- **THEN** the schema validation fails with a descriptive error listing missing fields
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
## ADDED Requirements

### Requirement: Provider abstraction interface
The system SHALL define an `EmailProvider` interface that all email providers implement, providing a unified API for email operations regardless of backend.

#### Scenario: Provider interface defines required methods
- **WHEN** a new provider is created
- **THEN** it MUST implement send(), read(), search(), draftSave(), draftList(), draftUpdate(), draftDelete(), and organize() methods

#### Scenario: Provider returns consistent message format
- **WHEN** any provider's read() or search() method is called
- **THEN** it returns messages in a standardized format with id, subject, from, to, date, body, isRead, labels, and folder fields

### Requirement: Gmail provider implementation
The system SHALL provide a Gmail provider using the Google Gmail API with OAuth2 authentication.

#### Scenario: Gmail provider authenticates via OAuth2
- **WHEN** Gmail provider is initialized with valid OAuth2 credentials
- **THEN** it obtains an access token and can make authenticated API requests

#### Scenario: Gmail provider sends email
- **WHEN** Gmail provider's send() method is called with valid message content
- **THEN** it sends the email via Gmail API and returns the message id

#### Scenario: Gmail provider reads inbox
- **WHEN** Gmail provider's read() method is called with folder="inbox"
- **THEN** it returns inbox messages via Gmail API's users.messages.list endpoint

#### Scenario: Gmail provider handles token refresh
- **WHEN** Gmail provider's access token expires during an operation
- **THEN** it automatically refreshes the token using the refresh token and retries the operation

### Requirement: MS Graph provider implementation
The system SHALL provide an MS Graph provider using the Microsoft Graph API with OAuth2 authentication.

#### Scenario: MS Graph provider authenticates via OAuth2
- **WHEN** MS Graph provider is initialized with valid OAuth2 credentials
- **THEN** it obtains an access token and can make authenticated Graph API requests

#### Scenario: MS Graph provider sends email
- **WHEN** MS Graph provider's send() method is called with valid message content
- **THEN** it sends the email via Microsoft Graph API and returns the message id

#### Scenario: MS Graph provider reads inbox
- **WHEN** MS Graph provider's read() method is called with folder="inbox"
- **THEN** it returns inbox messages via Microsoft Graph API's /me/messages endpoint

#### Scenario: MS Graph provider handles token refresh
- **WHEN** MS Graph provider's access token expires during an operation
- **THEN** it automatically refreshes the token using the refresh token and retries the operation

### Requirement: IMAP provider implementation
The system SHALL provide an IMAP provider as a universal fallback using username/password authentication.

#### Scenario: IMAP provider authenticates with credentials
- **WHEN** IMAP provider is initialized with valid host, port, username, and password
- **THEN** it establishes an IMAP connection and can execute IMAP commands

#### Scenario: IMAP provider sends email via SMTP
- **WHEN** IMAP provider's send() method is called with valid message content
- **THEN** it sends the email via SMTP and returns success

#### Scenario: IMAP provider reads inbox
- **WHEN** IMAP provider's read() method is called with folder="inbox"
- **THEN** it fetches messages via IMAP FETCH commands and returns them in the standard format

#### Scenario: IMAP provider handles connection errors
- **WHEN** IMAP provider cannot connect to the server
- **THEN** it throws a descriptive error with the connection failure details

### Requirement: Provider factory and selection
The system SHALL provide a factory that creates the appropriate provider instance based on configuration.

#### Scenario: Factory creates Gmail provider when configured
- **WHEN** config specifies provider="gmail" with OAuth2 credentials
- **THEN** factory returns a Gmail provider instance

#### Scenario: Factory creates MS Graph provider when configured
- **WHEN** config specifies provider="graph" with OAuth2 credentials
- **THEN** factory returns an MS Graph provider instance

#### Scenario: Factory creates IMAP provider when configured
- **WHEN** config specifies provider="imap" with host, port, username, and password
- **THEN** factory returns an IMAP provider instance

#### Scenario: Factory returns null when no provider configured
- **WHEN** no email provider is configured in config.yaml
- **THEN** factory returns null and email tools gracefully report "no provider configured"
Loading