> ## Documentation Index
> Fetch the complete documentation index at: https://casparser.in/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Inbound Email API

> Create dedicated email addresses for investors to forward CAS statements

## Overview

The Inbound Email API lets you create unique email addresses (like `ie_xyz@import.casparser.in`) where your users can forward their CAS statements. When an email arrives, we validate the sender, upload attachments to cloud storage, and deliver the result to your app.

**Use Case**: Lower-friction alternative to OAuth or manual file upload.

***

## Delivery options

`callback_url` is **optional**. It controls how you receive parsed files:

<CardGroup cols={2}>
  <Card title="With callback_url" icon="webhook">
    We `POST` parsed files to your URL whenever an investor forwards a CAS.
    Files also remain retrievable via `GET /v4/inbound-email/{id}/files` for 48h.

    **Best for:** server-side workflows, recurring statement ingestion.
  </Card>

  <Card title="Without callback_url" icon="browser">
    Retrieve files via `GET /v4/inbound-email/{id}/files`. The address is
    scoped to a 30-min sliding session (each poll extends it). Used by the
    Portfolio Connect widget when `enableInboundEmail: true`.

    **Best for:** onboarding flows, frontend-heavy apps with no backend.
  </Card>
</CardGroup>

Sender validation, file format, and security guarantees are identical regardless of `callback_url`.

***

## How It Works

```mermaid theme={null}
sequenceDiagram
    participant Dev as Your App
    participant API as CAS Parser API
    participant User as Investor
    participant Email as Email Provider
    participant Webhook as Your Webhook
    
    Dev->>API: POST /v4/inbound-email
    API-->>Dev: ie_xyz@import.casparser.in
    Dev->>User: Display: "Forward CAS to ie_xyz@..."
    User->>Email: Forwards CAS email
    Email->>API: Email received at ie_xyz@...
    API->>API: Validate sender (CDSL/NSDL/CAMS/KFintech)
    API->>Webhook: POST with email details + attachment URLs
    Webhook-->>API: 200 OK
```

***

## Quick Start

<Note>
  The Quick Start below provides a `callback_url` for webhook delivery. For a frontend-only flow with no backend, see [Use with Portfolio Connect](#use-with-portfolio-connect).
</Note>

### 1. Create an Inbound Email

<CodeGroup>
  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.casparser.in/v4/inbound-email",
      headers={"x-api-key": "your-api-key"},
      json={
          "callback_url": "https://yourapp.com/webhooks/cas-email",
          "allowed_sources": ["cdsl", "nsdl"],  # Optional filter
          "reference": "user_12345",            # Your internal ID
          "metadata": {"plan": "premium"}       # Optional key-value pairs
      }
  )

  data = response.json()
  print(f"Forward emails to: {data['email']}")
  # ie_a1b2c3d4e5f6@import.casparser.in
  ```

  ```javascript Node.js theme={null}
  const response = await fetch('https://api.casparser.in/v4/inbound-email', {
    method: 'POST',
    headers: {
      'x-api-key': 'your-api-key',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      callback_url: 'https://yourapp.com/webhooks/cas-email',
      allowed_sources: ['cdsl', 'nsdl'],
      reference: 'user_12345',
      metadata: { plan: 'premium' }
    })
  });

  const data = await response.json();
  console.log(`Forward emails to: ${data.email}`);
  // ie_a1b2c3d4e5f6@import.casparser.in
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.casparser.in/v4/inbound-email \
    -H "x-api-key: your-api-key" \
    -H "Content-Type: application/json" \
    -d '{
      "callback_url": "https://yourapp.com/webhooks/cas-email",
      "allowed_sources": ["cdsl", "nsdl"],
      "reference": "user_12345",
      "metadata": {"plan": "premium"}
    }'
  ```
</CodeGroup>

**Response (`201 Created`):**

```json theme={null}
{
  "inbound_email_id": "ie_a1b2c3d4e5f6",
  "email": "ie_a1b2c3d4e5f6@import.casparser.in",
  "callback_url": "https://yourapp.com/webhooks/cas-email",
  "allowed_sources": ["cdsl", "nsdl"],
  "reference": "user_12345",
  "metadata": {"plan": "premium"},
  "status": "active",
  "created_at": "2025-02-22T20:00:00Z",
  "updated_at": "2025-02-22T20:00:00Z"
}
```

<Note>
  `status` here is the inbound email's lifecycle (`active` / `paused`).
</Note>

***

### 2. Display to User

Show the email address in your UI:

```
📧 Forward your CAS statement to:
   ie_a1b2c3d4e5f6@import.casparser.in
```

***

### 3. Handle Webhook

When the user forwards an email, we POST to your `callback_url`:

<CodeGroup>
  ```python Python (Flask) theme={null}
  from flask import Flask, request, jsonify
  import requests

  app = Flask(__name__)

  @app.route('/webhooks/cas-email', methods=['POST'])
  def handle_cas_webhook():
      data = request.get_json()
      
      # Process the inbound email
      print(f"Forwarded by: {data['forwarded_by']}")  # Investor's email
      print(f"User reference: {data['reference']}")
      print(f"Files received: {data['count']}")
      
      # Download files (URLs expire in 48 hours)
      for file in data['files']:
          url = file['url']
          filename = file['filename']
          cas_type = file['cas_type']
          
          # Download to your storage
          response = requests.get(url)
          if response.status_code == 200:
              # Save to disk or upload to your storage
              with open(f"/tmp/{filename}", 'wb') as f:
                  f.write(response.content)
              print(f"Downloaded {cas_type}: {filename}")
      
      return jsonify({"status": "success"})
  ```

  ```javascript Node.js (Express) theme={null}
  const express = require('express');
  const fetch = require('node-fetch');
  const fs = require('fs');

  const app = express();
  app.use(express.json());

  app.post('/webhooks/cas-email', async (req, res) => {
    const data = req.body;
    
    console.log(`Forwarded by: ${data.forwarded_by}`);  // Investor's email
    console.log(`User reference: ${data.reference}`);
    console.log(`Files received: ${data.count}`);
    
    // Download files (URLs expire in 48 hours)
    for (const file of data.files) {
      const url = file.url;
      const filename = file.filename;
      const casType = file.cas_type;
      
      // Download to your storage
      const response = await fetch(url);
      if (response.ok) {
        const buffer = await response.buffer();
        // Save to disk or upload to your storage
        fs.writeFileSync(`/tmp/${filename}`, buffer);
        console.log(`Downloaded ${casType}: ${filename}`);
      }
    }
    
    res.json({ status: 'success' });
  });
  ```
</CodeGroup>

**Webhook Payload:**

```json theme={null}
{
  "inbound_email_id": "ie_a1b2c3d4e5f6",
  "forwarded_by": "investor@gmail.com",
  "reference": "user_12345",
  "metadata": {"plan": "premium"},
  "files": [
    {
      "message_id": "att_xyz789",
      "filename": "cdsl_20250222_att_xyz789.pdf",
      "original_filename": "JAN2026_AA03773313_TXN.pdf",
      "message_date": "2025-02-22",
      "cas_type": "cdsl",
      "sender_email": "ecas@cdslstatement.com",
      "size": 245000,
      "url": "https://storage.casparser.in/inbound-email/...",
      "expires_in": 172800
    }
  ],
  "count": 1
}
```

***

## Retrieving Files

The `GET /v4/inbound-email/{id}/files` endpoint returns every file that has been
forwarded to a given inbound email address. It's **available on every eligible inbound
email**, regardless of whether you configured a `callback_url`. Common uses:

* **Frontend polling** — the Portfolio Connect widget polls it automatically when `enableInboundEmail: true` (see below).
* **Backend polling** — use it instead of (or alongside) a webhook if a pull model fits your stack better.
* **Replay / backfill** — fetch files that were delivered via webhook if your endpoint was down or needs to re-process them.

```bash theme={null}
GET /v4/inbound-email/{inbound_email_id}/files?since=<last_cursor>&limit=20
```

**Response:**

```json theme={null}
{
  "status": "success",
  "files": [
    {
      "message_id": "att_xyz789",
      "filename": "cdsl_20250222_att_xyz789.pdf",
      "cas_type": "cdsl",
      "url": "https://storage.casparser.in/inbound-email/...",
      "expires_in": 172800,
      "received_at": "2025-02-22T20:01:12.000123+00:00"
    }
  ],
  "count": 1,
  "cursor": "2025-02-22T20:01:12.000123+00:00"
}
```

**Cursor pagination.** Pass the returned `cursor` as `since` on your next call
to receive only files that arrived after it. Timestamps are guaranteed
strictly monotonic at microsecond precision, so no entry is returned twice.
Persist the cursor on your side between polls.

<Note>
  `expires_in` differs depending on whether `callback_url` was set: long-lived presigned URLs (48h) when set, aligned with the session TTL when omitted. Treat the value as authoritative and refresh by re-polling if a download fails.
</Note>

**Retention.**

* With `callback_url` set: files retained **48 hours** from arrival.
* Without `callback_url`: files retained for the sliding session TTL; each call extends it.

***

## Use with Portfolio Connect

For frontend-only apps, the Portfolio Connect widget can drive the entire
flow: it creates a short-lived inbound email (without a `callback_url`),
displays it to the user, polls the retrieval endpoint above, and auto-parses
the forwarded PDF. No webhook required.

```tsx React theme={null}
import { PortfolioConnect } from '@cas-parser/connect';

<PortfolioConnect
  accessToken={accessToken}
  config={{
    enableInboundEmail: true,
    inboundEmail: {
      // All fields optional
      allowedSources: ['cdsl', 'nsdl'],
      reference: 'user_12345',
      metadata: { plan: 'premium' },
      pollIntervalMs: 3000,      // default
      sessionTimeoutMs: 1800000, // 30 min — matches backend TTL
    },
  }}
  onSuccess={(data, metadata) => {
    // metadata.source === 'INBOUND_EMAIL'
    // metadata.received_at === ISO timestamp
    console.log('Parsed:', data);
  }}
/>
```

The widget automatically:

* Creates an inbound email via `POST /v4/inbound-email` (no `callback_url`).
* Displays the address with a copy button.
* Polls `GET /v4/inbound-email/{id}/files` every few seconds.
* Parses the first file it receives and emits `onSuccess`.
* Deletes the inbound email on widget close (or lets the 30-min TTL expire it).

***

## Sender Validation

We automatically verify that forwarded emails originated from trusted CAS authorities:

* **CDSL** → [eCAS@cdslstatement.com](mailto:eCAS@cdslstatement.com)
* **NSDL** → [NSDL-CAS@nsdl.co.in](mailto:NSDL-CAS@nsdl.co.in)
* **CAMS** → [donotreply@camsonline.com](mailto:donotreply@camsonline.com)
* **KFintech** → [samfS@kfintech.com](mailto:samfS@kfintech.com)

**Forwarded emails** are supported — we extract the original sender from email headers and body.

***

## Managing Inbound Emails

### List All

```bash theme={null}
GET /v4/inbound-email?status=active&limit=50&offset=0
```

### Get Details

```bash theme={null}
GET /v4/inbound-email/ie_a1b2c3d4e5f6
```

### Delete

```bash theme={null}
DELETE /v4/inbound-email/ie_a1b2c3d4e5f6
```

***

## Best Practices

### 1. Download Attachments Promptly

Presigned URLs expire in **48 hours**. Download and store attachments in your own storage.

### 2. Use HTTPS for Callbacks

Production callback URLs must use HTTPS (HTTP is allowed for `localhost` during development).

### 3. Use `reference` for Correlation

Store your user ID in `reference` to map inbound emails back to your users.

### 4. Filter by `allowed_sources`

If you only need CDSL statements, set `"allowed_sources": ["cdsl"]` to reject others automatically.

***

## Billing

**0.2 credits** per validated email received, regardless of whether `callback_url` was set — charged on successful webhook delivery, or when the file is stored for polling.

Notes:

* Inbound email creation, listing, and polling reads (`GET /files`): **free**.
* Emails from unknown senders: **not billed**.
* Webhook-delivery failures (after retries): **still billed** — the email was valid.
* The Portfolio Connect widget incurs an additional **1.0 credit** when it auto-parses the received file (standard smart-parse cost).

***

## Error Handling

### Common Issues

| Error                        | Cause                                 | Solution                           |
| ---------------------------- | ------------------------------------- | ---------------------------------- |
| `callback_url must be HTTPS` | Using HTTP in production              | Use HTTPS or test with `localhost` |
| `Inbound email not found`    | Wrong ID or deleted                   | Check ID or recreate               |
| `alias already taken`        | Another inbound email uses this alias | Choose a different alias           |

### Webhook Delivery Failures

If your webhook endpoint is down or returns an error, we retry automatically with exponential backoff.

***

## Security

### Email Authentication

We validate:

* **Exact email whitelist**: Only emails from official CAS authority addresses
* **Header parsing**: Extract original sender from forwarded emails

### Webhook Security

* **HTTPS required**: Production callbacks must use HTTPS
* **Automatic retries**: Failed webhook deliveries are retried automatically
* **Attachment URLs**: Time-limited presigned URLs (48h expiry)

### Data Retention

* Inbound email configs with `callback_url` set: active indefinitely, marked inactive after 30 days without emails.
* Inbound email configs without `callback_url`: auto-expire after **30 minutes** of no polling activity (sliding TTL).
* Received file records (for polling): retained **48 hours** when `callback_url` is set; aligned with the session TTL otherwise.

***

## Use Cases

1. **Onboarding Flow**: "Forward your CAS to get started" — simpler than OAuth
2. **Recurring Updates**: Users forward monthly statements → auto-sync portfolios
3. **Offline Users**: Works for investors without OAuth access or app login

***

## Next Steps

* [Parse CAS PDFs](/guides/parsing) — parse the downloaded files
* [Gmail Inbox Import](/guides/gmail-inbox) — alternative: pull CAS from user's Gmail
* [CDSL Fetch](/guides/cdsl-fetch) — alternative: fetch CAS directly via OTP
