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

# Sending email

> Send messages, handle acceptance and partial bulk results, and inspect delivery outcomes.

Use a [verified identity](/email/identities) in the target region. The `from`
address must belong to that domain or one of its subdomains. Sending requires `email:SendMessage` on the
identity; template sends additionally require `email:SendTemplatedMessage` on the template.

## Send a message

<Tabs>
  <Tab title="Console">
    Open Email and **Sent messages** to send a message and inspect its status. Select
    a verified identity and supply the sender, recipients, subject, and body.
  </Tab>

  <Tab title="API">
    Send `POST /v1/sent-messages`. Use an `Idempotency-Key` header when retrying the
    same request so a network timeout does not produce another email.

    ```json theme={null}
    {
      "identity":"example.com",
      "from":"updates@example.com",
      "to":["reader@example.net"],
      "subject":"Your report is ready",
      "content":{"text":"Your report is ready to view."},
      "configuration_set":"transactional"
    }
    ```

    `configuration_set` is optional. Resource selectors accept a UUID, an
    account-scoped name, or a matching CRN. See [Authentication](/authentication)
    for the bearer token and `X-Account-Id` header.
  </Tab>

  <Tab title="CLI">
    Save the JSON request from the API tab as `request-1.json`. Use your configured account and region.

    ```bash theme={null}
    basaltic email sent-message send --from-file request-1.json
    ```
  </Tab>

  <Tab title="Go">
    Save the JSON request from the API tab as `request-1.json`. Configure credentials, account, and region through the SDK environment variables.

    ```go theme={null}
    package main

    import (
    	"context"
    	"encoding/json"
    	"fmt"
    	"os"

    	basaltic "github.com/basaltic-sh/sdk-go"
    	"github.com/basaltic-sh/sdk-go/email"
    )

    func readJSON(path string, value any) error {
    	data, err := os.ReadFile(path)
    	if err != nil {
    		return err
    	}
    	return json.Unmarshal(data, value)
    }

    func run() error {
    	ctx := context.Background()
    	cfg, err := basaltic.NewConfig(ctx)
    	if err != nil {
    		return err
    	}
    	client := email.New(cfg)
    	var request1 email.SendMessageRequest
    	if err := readJSON("request-1.json", &request1); err != nil {
    		return err
    	}
    	result1, err := client.SendMessage(ctx, &request1)
    	if err != nil {
    		return err
    	}
    	fmt.Printf("%+v\n", result1)
    	return nil
    }

    func main() {
    	if err := run(); err != nil {
    		fmt.Fprintln(os.Stderr, err)
    		os.Exit(1)
    	}
    }
    ```
  </Tab>
</Tabs>

`content` selects one explicit mode: `text` and/or `html`, `raw` for a complete
RFC 5322 message, or `template` with `template_data`. Text is never guessed to be
HTML. Supply `subject` for text/HTML; raw messages and templates provide their
own subject. The service validates the sender and signs outgoing mail with DKIM.
For reusable content, see [templates and batch sends](/email/templates).

## Acceptance and delivery

The response contains a `message` with its UUID, CRN, and `identity` ResourceRef.
Successful API acceptance queues the message. It does not prove inbox delivery.

| Status | Meaning |
| - | - |
| `queued` | Accepted and waiting for a sending worker. |
| `sending` | An SMTP delivery attempt is in progress. |
| `sent` | The receiving SMTP server accepted the message. |
| `failed` | The SMTP attempt or sending process failed. |
| `bounced` | A subsequent bounce was reported. |
| `complained` | A subsequent complaint was reported. |

Read `GET /v1/sent-messages/{message_id}` for `smtp_response`, `error_message`, and
outcome timestamps. One message record can contain several recipients; its
status is not an individual delivery receipt for each address. Send separate
messages when you need an independent outcome per recipient.

List messages with `GET /v1/sent-messages`, optionally filtering by `identity`,
`status`, or `recipient`. Use `meta.marker` for the next page while preserving
the filters. Read full decoded text/HTML at `/v1/sent-messages/{message_id}/content`, download
reconstructed MIME at `/raw`, and download individual files at
`/attachments/{attachment_id}`. The content read requires
`email:GetSentMessageContent` separately from metadata access.

## Suppressions and rejections

Account suppressions remove matching recipients before queueing. A request
fails when every recipient is suppressed. A configuration set can explicitly
override selected suppressed addresses; use that only after resolving the cause.
Create manual entries with `POST /v1/suppressions`; duplicate addresses return 409.
Use `PATCH /v1/suppressions/{suppression_id}` to change description or expiry.
Omission preserves expiry and null makes it permanent. Read and delete individual
suppressions by UUID on the same path.

Manual entries and entries created from bounce or complaint feedback appear in
**Suppressions**. Removing an entry permits future sends; it does not resend mail.

Rejected requests do not create queued message records. Inspect
`GET /v1/rejections` for the rejection reason, affected recipients, and optional
identity reference. A rejection may have no identity if one could not be resolved.

For aggregate sending and feedback trends, use [Email metrics](/email/metrics).
Per-message open and click details remain available at
`GET /v1/sent-messages/{message_id}/engagement`.

## Message retention and attachments

Sent message content is retained for **30 days**, included in the sending price.
The response includes `expires_at`. Message history is unavailable after expiry;
attachment objects remain governed by their bucket's lifecycle and Storage charges.

To attach existing objects, provide `attachments` when sending a message:

```json theme={null}
{
  "identity": "example.com",
  "from": "sender@example.com",
  "to": ["recipient@example.net"],
  "subject": "Your report",
  "content": {"text": "Your report is attached."},
  "attachments": [
    {"bucket":"email-files","key":"reports/report.pdf","name":"report.pdf"}
  ]
}
```

The bucket must belong to the same account and region. `storage:GetObject` is
required. An optional `version_id` selects a particular object version; the
service pins the selected version and checksum so a subsequent overwrite cannot
silently change an accepted email. A missing or changed attachment prevents delivery.

When `content.raw` contains a complete MIME message with binary attachments, also supply
`attachments_bucket`, using a bucket name, UUID, or CRN. Email extracts those
attachments into `email/{identity_id}/sent/{message_id}/` and retains only their
references alongside the message body. This path requires `storage:PutObject` and
`storage:GetObject` under that prefix. Attachments are read before DKIM signing.

Organization quotas limit complete encoded size, accepted sending recipients per
rolling second, and accepted sending recipients over rolling 24 hours. They apply
across accounts and identities in the region and count recipients remaining after
suppression filtering. Failed admission consumes no quota. See the
[quota and retention reference](/email/receiving#quotas-and-retention).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.