# Sending

Part of the [Ownbox SDK reference](https://www.ownbox.io/sdk/reference.md).

## m.send_dm

```python
m.send_dm(conversation, text, *, key, buttons=None, quick_replies=None)
```

Send one message on a conversation the machine claimed.

Through the inbox's own send path. `key` names the step: the same key on the same conversation is never sent twice. Refused when a person on the team replied since the claim, the person opted out, the Ownbox is stopped, the channel's window is closed (on Instagram, 24 hours after their own message) or the hourly cap is reached. `buttons`: up to 3 links, each `{"title", "url"}`. `quick_replies`: up to 13 titles; a tap sends the title back as their message, which `m.messages` then shows. Never raises.

**Needs in machine.yaml:** `send: [{channel: dm, per_day: N}]`

**Returns:** `{"status": "sent" | "duplicate" | "refused" | "unknown", "message_id", "reason", "code"}`. "unknown" means it may have gone: don't send it again. Decide on `code`: "taken_over", "opted_out", "box_stopped", "window_closed", "hourly_cap", "not_claimed", "too_old", "not_ready" (try again soon), "unchecked", or "refused".

Example (inside a job):

```python
for c in m.comments():
    convo = m.conversation_for(c)
    if convo and m.claim(convo, title="Quote Follow Up"):
        sent = m.send_dm(convo, "Is the quote still a good fit?", key="quote_check",
                         quick_replies=["Yes", "Not yet"])
        if sent["status"] != "sent":
            m.release(convo, note="Could not follow up on the quote.")
```

## m.reply_to_comment

```python
m.reply_to_comment(comment, text, *, key, quick_replies=None)
```

Answer a comment with a private message.

The first message to someone who has never written to the account, and the start of a conversation the machine can then claim. Instagram allows one private reply per comment, within 7 days of it. Never raises.

**Needs in machine.yaml:** `send: [{channel: dm, per_day: N}]`

**Returns:** The same answer as `m.send_dm`.

Example (inside a job):

```python
for c in m.comments()[:1]:
    answer = m.reply_to_comment(c, "Thanks! Here is the quote you asked about.", key="quote_reply")
```

## m.send_email

```python
m.send_email(person, draft, *, key, consent=None)
```

One email to a person who asked for it.

Through your Ownbox's consented path: it emails only a person who wrote in first. `person` is their id; `draft` is `{"subject", "text"}` (and `"to"` to pick an address when it knows several); `consent` names the conversation and the message in which they asked, and your Ownbox checks it. `key` names the step: the same key is never sent twice. A refusal for want of consent keeps a draft the owner can send from their own mail. Never raises.

**Needs in machine.yaml:** `send: [{channel: email, per_day: N}]`

**Returns:** `{"status": "sent" | "queued" | "duplicate" | "refused" | "unknown", "code", "reason", "message_id", "draft"}`. "unknown" means it may have gone: don't send it again. Decide on `code`: "no_consent", "opted_out", "unsubscribed", "box_stopped", "no_person", "no_address", "bad_draft", "failed" and others.

Example (inside a job):

```python
pid = m.person("email", "ava@example.com")
answer = m.send_email(pid, {"subject": "Your quote", "text": "Here it is, as promised."}, key="quote:41")
if answer["status"] not in ("sent", "queued", "duplicate"):
    m.save_setting("unsent_quote", answer.get("draft"))
```

## m.drafts

```python
m.drafts(*, limit=50)
```

The emails your Ownbox kept for the owner to send.

Newest first. Never raises.

**Needs in machine.yaml:** Nothing

**Returns:** `[{"id", "to", "subject", "text", "reason", "code", "created_at"}]`.

Example (inside a job):

```python
waiting = len(m.drafts(limit=20))
```

## m.drop_draft

```python
m.drop_draft(draft_id)
```

Let one of the machine's drafts go.

When the email went another way. Never raises.

**Needs in machine.yaml:** Nothing

**Returns:** True or False.

Example (inside a job):

```python
for d in m.drafts(limit=5):
    m.drop_draft(d["id"])
```

