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

# Send email

> POST /marketplace/messaging/send-email — transactional email with optional template or inline HTML — through the gu1 messaging API for customer communications.

Sends a single transactional email for the authenticated organization. Requires the **Email** marketplace integration and a configured messaging provider. See [Messaging overview](/en/api-reference/messaging/overview) for prerequisites, billing, and senders.

## Endpoint

```http theme={null}
POST https://api.gu1.ai/marketplace/messaging/send-email
```

## Headers

```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
X-Organization-ID: <uuid>   # optional; multi-org session users
```

## Request body

### Common

<ParamField body="to" type="string" required>
  Recipient email address.
</ParamField>

<ParamField body="fromEmail" type="string">
  Full From address (e.g. `noreply@your-domain.com`). The **domain** must be registered and **verified** for your organization under **Settings → Email → Domains**. You do not need a matching row under **Senders** when the domain is already verified. Mutually exclusive with `fromSenderId`.
</ParamField>

<ParamField body="fromSenderId" type="string (UUID)">
  ID of a sender from **Settings → Email → Senders**. Mutually exclusive with `fromEmail`.
</ParamField>

If you omit **both** `fromEmail` and `fromSenderId`, the API uses the **platform default Gu1 sender**. That is expected behavior, not an error.

## Custom sender (`fromEmail`)

When you send `fromEmail`, for example `example@my-domain.com`, the API validates the domain (`my-domain.com`) **before** attempting delivery. The response is always **`400`** with `{ "success": false, "error": "<English message>" }`; **the email is not sent** until the domain is verified.

| Situation                                         | API response                                                                                                                                                                    |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Domain **never added** in Gu1 for your org        | `Domain "my-domain.com" is not registered in Gu1. Add and verify it under Settings → Email → Domains before using sender "example@my-domain.com".`                              |
| Domain exists in Gu1 but **DNS not verified** yet | `Domain "my-domain.com" is not verified yet. Complete DNS records and click Verify under Settings → Email → Domains.`                                                           |
| Domain **verified**                               | Request continues; any local part on that domain is allowed (`example@…`, `noreply@…`, etc.) without a Senders row. If the address exists in Senders, its display name is used. |

Example rejection (unverified domain):

```json theme={null}
{
  "success": false,
  "error": "Domain \"my-domain.com\" is not verified yet. Complete DNS records and click Verify under Settings → Email → Domains."
}
```

<ParamField body="templateParams" type="object">
  Key-value map for `{{placeholders}}` in **subject**, **htmlBody**, **textBody**, or in a stored template. Values may be strings, numbers, or booleans (coerced for rendering). Default `{}`.
</ParamField>

### Mode A — Stored template

<ParamField body="templateId" type="string (UUID)" required>
  ID of a **message template** with channel **email** (org or system template).
</ParamField>

Do **not** send `htmlBody` or `textBody` in this mode.

<ParamField body="subject" type="string">
  Optional fallback if the template has no subject or the subject is empty after placeholder replacement.
</ParamField>

### Mode B — Inline content

Do **not** send `templateId`.

<ParamField body="subject" type="string" required>
  Email subject. May contain `{{variables}}` replaced via `templateParams`.
</ParamField>

<ParamField body="htmlBody" type="string">
  HTML body. Max \~500,000 characters. May include `{{variables}}`.
</ParamField>

<ParamField body="textBody" type="string">
  Plain-text body; used as multipart alternative when both HTML and text are sent, or converted to minimal HTML if HTML is omitted. Max \~500,000 characters. May include `{{variables}}`.
</ParamField>

You must provide **at least one** of `htmlBody` or `textBody`.

## Example — template

```bash theme={null}
curl -X POST https://api.gu1.ai/marketplace/messaging/send-email \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "user@example.com",
    "templateId": "550e8400-e29b-41d4-a716-446655440000",
    "templateParams": { "name": "Ada", "token": "482910" },
    "fromEmail": "noreply@yourdomain.com"
  }'
```

## Example — inline HTML + placeholders

```bash theme={null}
curl -X POST https://api.gu1.ai/marketplace/messaging/send-email \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "user@example.com",
    "subject": "Your code: {{token}}",
    "htmlBody": "<p>Hello, your code is <strong>{{token}}</strong></p>",
    "templateParams": { "token": "482910" }
  }'
```

## Example — plain text only

```bash theme={null}
curl -X POST https://api.gu1.ai/marketplace/messaging/send-email \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "user@example.com",
    "subject": "Notification",
    "textBody": "Line one\nLine two"
  }'
```

## Success response

```json theme={null}
{
  "success": true,
  "messageId": "provider-message-id"
}
```

Exact fields may match your `MessageDeliveryService` / provider response.

## Errors and HTTP status

Business and validation responses from this route use JSON like `{ "success": false, "error": "<English message>" }` unless noted. **All `error` strings are in English.** Invalid JSON bodies may return a different shape from the validator (e.g. Zod field issues) at **400**.

| Situation                                     | Typical status       | Example `error` (or note)                                                                                                                    |
| --------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Email integration off for the org             | **400**              | `Email integration is not active. Enable Email (global_sender_email) in Applications (Marketplace) for this organization.`                   |
| Messaging provider URL not set (server)       | **400**              | `MS_PROVIDER_URL is not configured on the server. Configure the messaging provider to send email.`                                           |
| Cost > 0 and no pack quota / credits          | **400**              | `Insufficient balance for this send (cost … credits). Add tokens…` (see response for full text)                                              |
| Send succeeded but billing failed             | **400**              | `Insufficient balance to record billing for this send. Current balance (…) is below required (…). …`                                         |
| Both `templateId` and inline body             | **400** (validation) | `Use either templateId + templateParams, or htmlBody/textBody only — not both.`                                                              |
| Neither template nor body                     | **400** (validation) | `Provide templateId or htmlBody/textBody.`                                                                                                   |
| Inline mode without `subject`                 | **400** (validation) | `subject is required when not using a template.`                                                                                             |
| Template UUID wrong / other org               | **404**              | `Template not found or not accessible for this organization.`                                                                                |
| Template exists but not email channel         | **400**              | `Template channel is "<channel>"; email is required.`                                                                                        |
| Template + empty subject after render         | **400**              | `The template has no subject or it is empty after replacing variables. Send subject in the request body or set the subject on the template.` |
| Both `fromSenderId` and `fromEmail`           | **400**              | `Send only one of fromSenderId or fromEmail, not both.`                                                                                      |
| Unknown `fromSenderId`                        | **400**              | `fromSenderId does not match a sender for this organization. Check Settings → Email → Senders.`                                              |
| `fromEmail` domain not registered for the org | **400**              | `Domain "…" is not registered in Gu1. Add and verify it under Settings → Email → Domains before using sender "…".`                           |
| Domain registered but DNS not verified        | **400**              | `Domain "…" is not verified yet. Complete DNS records and click Verify under Settings → Email → Domains.`                                    |
| Empty inline subject after `{{…}}`            | **400**              | `Subject is empty after replacing variables.`                                                                                                |
| Empty body after render                       | **400**              | `htmlBody or textBody is empty after rendering.`                                                                                             |
| No session org                                | **401**              | `{ "error": "Organization ID not found" }` (no `success` field)                                                                              |

After validation, the MS Provider may still return `{ "success": false, "error": "…" }` (e.g. provider rejection) at **200** or an error status depending on implementation—inspect `success` and `error` on every response.
