> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://docs.postnuvia.com/llms.txt.

# Guide: Sending & Receiving Email

> A step-by-step guide to the practical workflow of sending initial emails and handling replies to have a full conversation.

This guide walks you through the complete, practical workflow of an agent having a conversation. While the `Core Concepts` pages detail the individual API calls, this guide shows you how to stitch them together to create a functional conversational loop.

## The Foundation: Sending HTML & Text

As a quick reminder from our `Messages` documentation, it's a critical best practice to always provide both an `html` and a `text` version of your email. This ensures readability on all email clients and significantly improves deliverability.

```python
# Always provide both html and text when possible
client.inboxes.messages.send(
    inbox_id="outreach@postnuvia.com",
    to=["potential-customer@example.com"],
    subject="Following up",
    text="Hi Jane,\n\nThis is a plain-text version of our email.",
    html="<p>Hi Jane,</p><p>This is a <strong>rich HTML</strong> version of our email.</p>",
    labels=["outreach-campaign"]
)
```

## The Conversational Loop

A common task for an agent is to check for replies in an `Inbox` and then respond to them. While using `Webhooks` is the most efficient method for this, you can also build a simple polling mechanism.

Here's the step-by-step logic for a polling-based conversational agent.

#### 1. Find a Thread that Needs a Reply

First, you need to identify which conversations have new messages that your agent hasn't responded to. A great way to manage this is with `Labels`. You can list `Threads` in a specific `Inbox` that have an `unreplied` `Label`.

```python
# Find all threads in this inbox that are marked as unreplied
threadsRes = client.threads.list(
    labels = ["unreplied"]
)
if threadsRes.count == 0:
    print("No threads need a reply.")
else:
    # Let's work on the first unreplied thread
    thread_to_reply_to = threadsRes.thread[0]
```

```typescript
// Find all threads in this inbox that are marked as unreplied
const threadRes = await client.threads.list(
    {
        labels: [
            "huh"
        ]
    }
)

if (threadRes.count === 0) {
    console.log("No threads need a reply.");
} else {
    // Let's work on the first unreplied thread
    const threadToReplyTo = threadRes.threads[0];
}
```

#### 2. Get the Last Message ID from the Thread

To reply to a conversation, you need to reply to the *most recent message* in the `Thread`. You can get a specific `Thread` by its ID, which will contain a list of all its `Messages`. You'll then grab the ID of the last `Message` in that list.

```python
# Get the full thread object to access its messages
thread_details = client.threads.get(thread_to_reply_to.thread_id)

# The last message in the list is the one we want to reply to
last_message = thread_details.messages[-1]
message_id_to_reply_to = last_message.message_id
```

```typescript
// Get the full thread object to access its messages
const threadDetails = await client.threads.get('thread_id');

// The last message in the array is the one we want to reply to
const lastMessage = threadDetails.messages[threadDetails.messages.length - 1];
const messageIdToReplyTo = lastMessage.message_id;
```

> **Tip**
>
> Use `last_message.extracted_text` (or `extracted_html`) when you need
> just the new reply content, without quoted history.

#### 3. Send the Reply and Update Labels

Now that you have the `message_id` to reply to, you can send your agent's response. It's also a best practice to update the `Labels` on the original `Message` at the same time, removing the `unreplied` `Label` and adding a `replied` `Label` to prevent the agent from replying to the same message twice.

```python
# Send the reply
client.inboxes.messages.reply(
    inbox_id="support@postnuvia.com",
    message_id=message_id_to_reply_to,
    text="This is our agent's helpful reply!"
)

# Update the labels on the original message
client.inboxes.messages.update(
    inbox_id="support@postnuvia.com",
    message_id=message_id_to_reply_to,
    add_labels=["replied"],
    remove_labels=["unreplied"]
)
```

```typescript
// Send the reply
await client.inboxes.messages.reply("support@postnuvia.com", messageIdToReplyTo, {
    text: "This is our agent's helpful reply!",
});

// Update the labels on the original message
await client.inboxes.messages.update("support@postnuvia.com", messageIdToReplyTo, {
    addLabels: ["replied"],
    removeLabels: ["unreplied"],
});
```

**`CLI`**

```bash title="CLI"
# send the reply
postnuvia inboxes messages reply \
  --inbox-id support@postnuvia.com \
  --message-id <message_id> \
  --text "This is our agent's helpful reply!"

# update the labels on the original message
postnuvia inboxes messages update \
  --inbox-id support@postnuvia.com \
  --message-id <message_id> \
  --add-labels replied \
  --remove-labels unreplied
```

> **Real-Time Processing with Webhooks**
>
> For production applications, polling is inefficient. The best way to handle incoming replies is to use `Webhooks`. This allows PostNuvia to notify your agent instantly when a new `Message` arrives, so you can reply in real-time.
>
> [**Learn how to set up `Webhooks` →**](/webhooks-overview)

## Scheduling Emails

Instead of sending immediately, you can schedule emails for a future time—perfect for delivering messages during business hours or spacing out outreach.

Create a `Draft` with the `send_at` field and PostNuvia handles the rest. The email is automatically sent at the specified time.

**`Python`**

```python title="Python"
from datetime import datetime, timedelta

# Schedule for tomorrow at 9 AM UTC
send_time = (datetime.utcnow() + timedelta(days=1)).replace(hour=9, minute=0, second=0)

client.inboxes.drafts.create(
    inbox_id="outreach@postnuvia.com",
    to=["prospect@example.com"],
    subject="Quick question about your workflow",
    text="Hi, I noticed you're using...",
    html="<p>Hi, I noticed you're using...</p>",
    send_at=send_time.isoformat() + "Z"
)
```

**`TypeScript`**

```typescript title="TypeScript"
// Schedule for tomorrow at 9 AM UTC
const sendTime = new Date();
sendTime.setUTCDate(sendTime.getUTCDate() + 1);
sendTime.setUTCHours(9, 0, 0, 0);

await client.inboxes.drafts.create(
    "outreach@postnuvia.com",
    {
        to: ["prospect@example.com"],
        subject: "Quick question about your workflow",
        text: "Hi, I noticed you're using...",
        html: "<p>Hi, I noticed you're using...</p>",
        sendAt: sendTime.toISOString()
    }
);
```

> **Info**
>
> For more details on scheduled sending—including how to cancel, reschedule, list scheduled drafts, and build conditional follow-up workflows—see the [**Drafts**](/drafts) page.