> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.getdial.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.getdial.ai/_mcp/server.

# Receive an SMS

> Receive an inbound SMS — such as a one-time code — by consuming the account event stream.

When someone texts one of your Dial numbers, Dial emits a `message.received` event. The most common use is **catching a one-time code**: an agent triggers an SMS verification on some service, then waits for the code to arrive on its Dial number.

You don't poll for messages — you wait for the event. There are two ways to wait.

For a one-time code, an active wait (below) is the right tool. But note the underlying event stream is **presence-based**, not at-least-once — a long-lived listener replays missed events only if it reconnects within 2 minutes. For guaranteed delivery, register an [at-least-once webhook](/documentation/platform/webhooks).

## Wait once

Block until the next inbound message arrives (or a timeout elapses). Ideal for a single code.

```bash title="CLI"
# Blocks until an inbound SMS arrives, prints the event as JSON, then exits.
dial wait-for message.received --timeout 60 --json
```

```python title="Python"
from dial_sdk import DialClient, DialConfig

dial = DialClient(DialConfig(api_key="sk_live_..."))
async with dial.new_events_connection() as conn:
    async for event in conn:
        if event["type"] == "message.received":
            print("code text:", event["data"]["body"])
            break
```

```typescript title="Node"
import { DialClient } from "@getdial/sdk";

const dial = new DialClient({ apiKey: process.env.DIAL_API_KEY! });
const conn = await dial.newEventsConnection();
try {
  for await (const event of conn) {
    if (event.type === "message.received") {
      console.log("code text:", event.data.body);
      break;
    }
  }
} finally {
  await conn.close();
}
```

```bash title="cURL"
curl -X POST https://api.getdial.ai/v1/events/wait \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"eventType":"message.received","timeout":60}'
```

The CLI and REST `events/wait` return as soon as a matching event arrives. If nothing arrives within the timeout, the REST endpoint responds `408`; the CLI exits non-zero.

## Filter to a specific number

If you have several numbers, match on event fields so you only wake for the right one:

```bash
dial wait-for message.received --field to=+14155550123 --timeout 60 --json
```

Use `--field name=value` for exact matches or `--regex name=pattern` for patterns (both repeatable).

## Extract the code

An inbound `message.received` event carries the message text at `data.body`. Pull the code out with a regex in your agent — for example, the first 6-digit run:

```python
import re
code = re.search(r"\b(\d{6})\b", event["data"]["body"]).group(1)
```

## Receive media (MMS and iMessage attachments)

When an inbound message carries media (pictures, etc.) — an MMS on an SMS number or an attachment on an iMessage number — the event's `data.media` array lists each attachment with a stable, unauthenticated `url` hosted by Dial — fetch or render it directly, no API key needed. `contentType` gives the MIME type. Text-only events have an empty `media` array.

```python
for item in event["data"]["media"]:
    print(item["contentType"], item["url"])
```

Media is also returned on `GET /api/v1/messages` (each message's `media` array), so you can recover attachments after the fact without having caught the event.

## Next

#### [Stream account events](/documentation/platform/stream-account-events)

Keep a long-lived connection open.

#### [Listen service](/documentation/cli/listen-service)

Capture events in the background.