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

# On Receive

> Listen for real-time email events with typed callbacks.

# On Receive (Streaming)

The `onReceive` method provides a persistent connection to our event stream with **typed callbacks** for each event type. Unlike webhooks, you don't need to expose a public URL or handle signature verification.

<Info>
  This is the recommended way to handle reply-to-bot flows, chat interfaces, or real-time dashboards.
</Info>

## Zero-Config Usage

Just pass the callbacks you need. **No cursor tracking, no deduplication logic** — we handle it all server-side.

```typescript theme={null}
import { createClient } from '@msgmorph/sdk'

const client = createClient({ apiKey: 'em_...' })

// Listen for replies - events are auto-acknowledged after your handler completes
client.onReceive({
  onReplied: (reply) => {
    console.log('Reply from:', reply.from)
    console.log('Message:', reply.body)
  }
})
```

## How It Works

Behind the scenes, `onReceive`:

* **Server-side tracking**: Uses your API key to track which events you've processed
* **Automatic acknowledgment**: Events are acknowledged after your handler completes
* **Replay on reconnect**: Unacknowledged events are replayed if your app disconnects
* **No duplicates**: You never see the same event twice

This means if your server crashes mid-processing, you'll get those events again on restart. No data loss.

## All Event Handlers

```typescript theme={null}
const controller = client.onReceive({
  batchSize: 10,      // Optional: events per batch

  onSent: (event) => console.log('Sent:', event.subject),
  onDelivered: (event) => console.log('Delivered to:', event.recipients),
  onReplied: (event) => console.log('Reply:', event.body),
  onBounced: (event) => console.log('Bounced:', event.bounceType),
  onFailed: (event) => console.log('Failed:', event.error),
  onComplained: (event) => console.log('Spam complaint'),
  onRejected: (event) => console.log('Rejected:', event.reason),
  onDelayed: (event) => console.log('Delayed:', event.delayType),

  onError: (err) => console.error('Stream error:', err)
})

// Stop listening when done
controller.abort()
```

## Event Types

<ResponseField name="onSent" type="function">
  Called when an email is successfully queued for sending.
</ResponseField>

<ResponseField name="onDelivered" type="function">
  Called when the receiving mail server confirms delivery.
</ResponseField>

<ResponseField name="onReplied" type="function">
  Called when a recipient replies to your email.
</ResponseField>

<ResponseField name="onBounced" type="function">
  Called when an email bounces (hard or soft bounce).
</ResponseField>

<ResponseField name="onFailed" type="function">
  Called when sending fails permanently.
</ResponseField>

<ResponseField name="onComplained" type="function">
  Called when a recipient marks your email as spam.
</ResponseField>

## Manual Acknowledgment

For critical workflows where you need to ensure database writes complete before acknowledging:

```typescript theme={null}
client.onReceive({
  ackMode: 'manual',  // Don't auto-ack
  
  onReplied: async (event) => {
    await saveToDatabase(event)  // Make sure this completes
    // Note: Manual ack not yet implemented via SDK
    // Server will replay unacked events on next connect
  }
})
```

<Tip>
  For most use cases, auto-ack (the default) is recommended. It provides exactly-once semantics for well-behaved handlers.
</Tip>

## Resiliency

The stream automatically handles keep-alives (heartbeats). If the connection drops:

1. Your `onError` callback fires
2. SDK auto-reconnects with exponential backoff
3. Server replays any unacknowledged events
4. Your handlers process them normally
