Skip to main content
Glama
bradsjm

mail-smtp-mcp

README.md
# mail-smtp-mcp

A TypeScript MCP (Model Context Protocol) server for sending email via SMTP, designed for code agents. Exposes a compact, LLM-optimized tool surface with strict policy controls and validation.

## Quick Start

### Primary Usage (Recommended)

Run directly via npx with your MCP client:

```bash
npx -y @bradsjm/mail-smtp-mcp
```

Configure the required environment variables before starting (see [Configuration](#configuration) below).

### Local Development

```bash
pnpm install
pnpm dev
```

Build for production:

```bash
pnpm build
```

## Configuration

The server loads configuration from environment variables. A local `.env` file is also supported via `dotenv` (do not commit secrets).

### Account Configuration

Configure one or more SMTP accounts using the following pattern:

```
MAIL_SMTP_<ACCOUNT_ID>_<SETTING>=value
```

| Setting | Required | Default | Description |
|---------|----------|---------|-------------|
| `HOST` | Yes | - | SMTP server hostname |
| `PORT` | No | 587 (if SECURE=false), 465 (if SECURE=true) | SMTP server port |
| `SECURE` | No | `false` | Use SSL/TLS (true) or STARTTLS (false) |
| `USER` | Yes | - | SMTP authentication username |
| `PASS` | Yes | - | SMTP authentication password |
| `FROM` | No | - | Default sender email address |

**Example - Default Account:**

```bash
MAIL_SMTP_DEFAULT_HOST=smtp.gmail.com
MAIL_SMTP_DEFAULT_PORT=587
MAIL_SMTP_DEFAULT_SECURE=false
MAIL_SMTP_DEFAULT_USER=you@gmail.com
MAIL_SMTP_DEFAULT_PASS=your-app-password
MAIL_SMTP_DEFAULT_FROM=you@gmail.com
```

**Example - Multiple Accounts:**

```bash
# Default account
MAIL_SMTP_DEFAULT_HOST=smtp.gmail.com
MAIL_SMTP_DEFAULT_USER=you@gmail.com
MAIL_SMTP_DEFAULT_PASS=app-password-1

# Work account
MAIL_SMTP_WORK_HOST=smtp.company.com
MAIL_SMTP_WORK_PORT=465
MAIL_SMTP_WORK_SECURE=true
MAIL_SMTP_WORK_USER=you@company.com
MAIL_SMTP_WORK_PASS=company-password
MAIL_SMTP_WORK_FROM=you@company.com
```

### Send Gate

Sending is **disabled by default** for security. Enable with:

```bash
MAIL_SMTP_SEND_ENABLED=true
```

### Allowlist Policy (Optional)

Restrict recipients to specific domains or email addresses:

```bash
MAIL_SMTP_ALLOWLIST_DOMAINS=example.com,example.org
MAIL_SMTP_ALLOWLIST_ADDRESSES=alice@example.com,bob@example.org
```

### Policy Limits

Configure operational limits (defaults shown):

| Variable | Default | Description |
|----------|---------|-------------|
| `MAIL_SMTP_MAX_RECIPIENTS` | 10 | Maximum recipients per message |
| `MAIL_SMTP_MAX_MESSAGE_BYTES` | 2,500,000 | Maximum total message size (~2.4 MB) |
| `MAIL_SMTP_MAX_ATTACHMENTS` | 5 | Maximum number of attachments |
| `MAIL_SMTP_MAX_ATTACHMENT_BYTES` | 2,000,000 | Maximum size per attachment (~1.9 MB) |
| `MAIL_SMTP_MAX_TEXT_CHARS` | 20,000 | Maximum characters in plain text body |
| `MAIL_SMTP_MAX_HTML_CHARS` | 50,000 | Maximum characters in HTML body |
| `MAIL_SMTP_CONNECT_TIMEOUT_MS` | 10,000 | SMTP connection timeout (ms) |
| `MAIL_SMTP_SOCKET_TIMEOUT_MS` | 20,000 | SMTP socket timeout (ms) |

## Tools Overview

| Tool | Purpose | Requires Send Enabled? |
|------|---------|----------------------|
| `smtp_list_accounts` | List configured SMTP accounts | No |
| `smtp_verify_account` | Verify SMTP connectivity without sending email | No |
| `smtp_send_message` | Send or validate an outbound email | Yes (unless dry_run) |

## Tool Details

### `smtp_list_accounts`

Lists configured SMTP accounts with non-sensitive metadata.

**Input:**
```json
{
  "account_id": "default"  // Optional - filter by specific account
}
```

**Response:**
```json
{
  "summary": "Found 2 configured SMTP account(s).",
  "data": {
    "accounts": [
      {
        "account_id": "default",
        "host": "smtp.gmail.com",
        "port": 587,
        "secure": false,
        "default_from": "you@gmail.com"
      },
      {
        "account_id": "work",
        "host": "smtp.company.com",
        "port": 465,
        "secure": true
      }
    ]
  },
  "_meta": {
    "send_enabled": true,
    "limits": {
      "max_recipients": 10,
      "max_message_bytes": 2500000,
      "max_attachments": 5,
      "max_attachment_bytes": 2000000
    }
  }
}
```

### `smtp_verify_account`

Verifies that an SMTP account can connect and authenticate without sending an email.

**Input:**
```json
{
  "account_id": "default"  // Defaults to "default"
}
```

**Verification Flow:**
```
┌─────────────────┐
│   Load Account  │
│   Config        │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Create SMTP     │
│ Transport       │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Verify Connect  │
│ & Authenticate  │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Return Status   │
│ (ok / error)    │
└─────────────────┘
```

**Response:**
```json
{
  "summary": "SMTP account default verified successfully.",
  "data": {
    "account_id": "default",
    "status": "ok"
  }
}
```

### `smtp_send_message`

Sends or validates an outbound email via SMTP with comprehensive validation.

**Input:**
```json
{
  "account_id": "default",           // Defaults to "default"
  "from": "sender@example.com",      // Optional - override account default
  "to": ["recipient@example.com"],   // Required - string or array
  "cc": ["cc@example.com"],          // Optional
  "bcc": ["bcc@example.com"],        // Optional
  "reply_to": "reply@example.com",   // Optional
  "subject": "Test Email",           // Required
  "text_body": "Plain text here",    // Optional
  "html_body": "<p>HTML here</p>",  // Optional
  "attachments": [                   // Optional
    {
      "filename": "document.pdf",
      "content_base64": "JVBERi0xL...",
      "content_type": "application/pdf"
    }
  ],
  "dry_run": false                   // Optional - validate without sending
}
```

**Send Flow:**
```
┌──────────────────┐
│ Validate Input   │
│ - Email format   │
│ - Header inject. │
│ - Body lengths   │
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│ Enforce Policy   │
│ - Recipient      │
│   allowlist      │
│ - Size limits    │
│ - Attachment     │
│   limits         │
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│ Build Message    │
│ - Parse attach-  │
│   ments (base64) │
│ - Estimate size  │
└────────┬─────────┘
         │
         ▼
         ┌──────────┐
         │ dry_run? │
         └────┬─────┘
              │
         ┌────┴────┐
         │         │
        Yes       No
         │         │
         ▼         ▼
┌────────────┐  ┌──────────────┐
│ Return     │  │ Send via     │
│ Validation │  │ SMTP         │
│ Results    │  │ Transport    │
└────────────┘  └──────┬───────┘
                       │
                       ▼
              ┌────────────────┐
              │ Return Send    │
              │ Results        │
              └────────────────┘
```

**Response (Success):**
```json
{
  "summary": "Sent message to 3 recipient(s).",
  "data": {
    "account_id": "default",
    "dry_run": false,
    "envelope": {
      "from": "sender@example.com",
      "to": ["recipient@example.com"],
      "cc": ["cc@example.com"],
      "bcc": ["bcc@example.com"]
    },
    "message_id": "<abc123@example.com>",
    "accepted": ["recipient@example.com", "cc@example.com", "bcc@example.com"],
    "rejected": []
  }
}
```

**Response (Dry Run):**
```json
{
  "summary": "Validated message for 3 recipient(s).",
  "data": {
    "account_id": "default",
    "dry_run": true,
    "envelope": {
      "from": "sender@example.com",
      "to": ["recipient@example.com"],
      "cc": ["cc@example.com"],
      "bcc": ["bcc@example.com"]
    },
    "size_bytes_estimate": 12500
  }
}
```

## Response Format

All tool responses use a consistent JSON structure returned as MCP text content:

```
{
  "summary": "Human-readable status message",
  "data": { /* Tool-specific structured data */ },
  "_meta": { /* Optional metadata (e.g., policy info) */ }
}
```

## Hard Maximum Limits

The following absolute upper bounds cannot be exceeded regardless of policy settings:

| Resource | Hard Limit |
|----------|------------|
| Recipients per message | 50 |
| Attachments per message | 10 |
| Attachment size | 5,000,000 bytes (~4.8 MB) |
| Plain text body characters | 100,000 |
| HTML body characters | 200,000 |
| Subject characters | 256 |

## Security Notes

- **No HTTP Transport**: This server only supports stdio transport for security. HTTP endpoints are intentionally not exposed.
- **Credential Handling**: SMTP credentials are never logged or returned in tool responses.
- **Header Injection Protection**: All email addresses and subject fields are validated to reject CR/LF characters that could be used for header injection.
- **Filename Safety**: Attachment filenames are validated to prevent path traversal attacks.
- **Send Gate**: Sending is disabled by default and must be explicitly enabled via `MAIL_SMTP_SEND_ENABLED=true`.
- **Dry Run Mode**: Use `dry_run: true` to validate messages and policies without actually sending emails.

## MCP Client Integration

### Standard npx Configuration

Most MCP-enabled applications can spawn this server using:

```json
{
  "command": "npx",
  "args": ["-y", "@bradsjm/mail-smtp-mcp"],
  "env": {
    "MAIL_SMTP_DEFAULT_HOST": "smtp.gmail.com",
    "MAIL_SMTP_DEFAULT_PORT": "587",
    "MAIL_SMTP_DEFAULT_SECURE": "false",
    "MAIL_SMTP_DEFAULT_USER": "you@gmail.com",
    "MAIL_SMTP_DEFAULT_PASS": "your-app-password",
    "MAIL_SMTP_DEFAULT_FROM": "you@gmail.com",
    "MAIL_SMTP_SEND_ENABLED": "true"
  }
}
```

### Development Configuration

For local development without publishing:

```json
{
  "command": "node",
  "args": ["/path/to/mail-smtp-mcp/dist/index.js"],
  "env": {
    "MAIL_SMTP_DEFAULT_HOST": "smtp.gmail.com",
    "MAIL_SMTP_DEFAULT_USER": "you@gmail.com",
    "MAIL_SMTP_DEFAULT_PASS": "your-app-password",
    "MAIL_SMTP_SEND_ENABLED": "true"
  }
}
```

## Development

```bash
# Install dependencies
pnpm install

# Run in development mode (TypeScript)
pnpm dev

# Build for production
pnpm build

# Run tests
pnpm test

# Type checking
pnpm typecheck

# Linting
pnpm lint

# Format code
pnpm format:write
```

Run all quality checks:

```bash
pnpm check
```

## License

See LICENSE file for details.