# Send mail

`POST /members/send` sends one email in a project. The effective principal
needs active project membership and `mail.send`. Use the
[production base and JSON request format](https://m7.org/docs/api/api.mail.m7.org/README.md) with a Bearer access
token and, when needed, an [organization selection](https://m7.org/docs/api/api.mail.m7.org/workspaces.md).

## Request fields

| Field | Type | Required/default | Meaning |
| --- | --- | --- | --- |
| `project_id` | UUID string | Required for normal requests | Project returned by project creation or listing. |
| `method` | string | Defaults to `email` | `email` or blank uses email; other delivery methods fail with `400`. |
| `sender_email` | string | Required | From email address; alias `from_email`. |
| `recipient_email` | string | Required | One recipient email address; alias `to_email`. |
| `sender_name` | string or null | Optional | From display name; alias `from_name`. Blank/omitted uses the service's configured display name when available. |
| `recipient_name` | string or null | Optional | Recipient display name; alias `to_name`. |
| `reply_to_email` | string or null | Optional | Reply-To address. |
| `subject` | string | Defaults to an empty string | Subject line. |
| `body_text` | string or null | At least one nonblank body | Plain text; alias `body`. |
| `body_html` | string or null | At least one nonblank body | HTML; when supplied alone, a plain-text alternative is derived. |

Use canonical fields consistently. Email addresses are trimmed, validated,
and lowercased. Display names and subject are trimmed and must not contain
CR/LF characters. Body line endings normalize to LF; wholly blank bodies are
treated as absent. Supplying both text and HTML creates alternatives. Sender
and Reply-To fields describe the message; they do not select its owner or
grant sending authority.

```json
{
  "project_id":"<project-uuid>",
  "sender_email":"sender@example.com",
  "sender_name":"Example App",
  "recipient_email":"recipient@example.com",
  "subject":"Your receipt",
  "body_text":"Thank you for your order."
}
```

Normal callers must provide `project_id`. A machine deployment may have a
server-configured default project; omission works only for an app principal
with that configuration. The default does not grant membership or
`mail.send`. Include the explicit project ID in portable integrations.

The service sets the owner, actor, and source. Caller-supplied `owner`,
`actor`, `owner_scope`, `organization_id`, and `client_id` fail with `400`.
Use only documented message and workspace fields.

## Queue response and logging

A successful response has `status: 1`, `comment: "OK"`, and these `data` fields:

| Field | Meaning |
| --- | --- |
| `success` | `true` when accepted by the mail transport. |
| `method`, `provider`, `transport` | `email`, `postfix`, and `sendmail`. |
| `status`, `queued` | `queued` and `true`; no recipient-delivery confirmation. |
| `logged` | Whether an outbound-attempt record was stored. |
| `item` | Stored log record when available; otherwise an empty object/array. |
| `message_id` | Composed message identifier, or `null`. |
| `log_error` | `null` when logging succeeded; error detail when logging failed. |

```json
{
  "status":1,
  "comment":"OK",
  "data":{
    "success":true,"method":"email","provider":"postfix",
    "transport":"sendmail","status":"queued","queued":true,
    "logged":false,"item":[],"message_id":"<message-id>",
    "log_error":"<logging-error>"
  }
}
```

Transport failure returns an error and attempts to store a `failed` record.
A message can be accepted even if logging fails; `logged: false` is not a send
failure. Keep queue acceptance and log persistence as separate outcomes.

## Retry handling

The API does not provide an idempotency key or deduplication guarantee. A
timeout or connection loss may occur after transport acceptance. A repeated
send can produce another message. Reconcile with your application state and
[project logs](https://m7.org/docs/api/api.mail.m7.org/send-log.md) before deciding to resend; absence of a log alone
cannot prove the message was not queued.
