> ## 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.

# Receiving email

> Route recipient addresses to Notifications topics and retrieve accepted email safely.

Configure receiving under a verified domain identity. The receiving configuration
contains only `enabled` and `attachments_bucket`. The identity's `dns_records`
contains sending and receiving DNS guidance, including the complete MX value.
All identities use the same ownership and IAM checks.

## Enable receiving and add a rule

<Tabs>
  <Tab title="Console">
    Open Email → **Identities** and select the domain.

    1. In **Settings**, choose an **Attachments bucket** in the **Receiving** card,
       turn on the switch, and select **Save**. The bucket must belong to the same
       account and region.
    2. Open **Recipient rules** and select **Create rule**. Choose an exact
       address, address prefix, or all addresses, then pick a Notifications
       topic in the same account and region from the resource selector.
    3. Open **DNS records** and publish the MX record from **Receiving records**.
       **Sending records** contains the records used for verification and sending,
       including any custom MAIL FROM records.

    The recipient rules table shows each destination topic by name. Select a
    rule to access its deletion controls, or use its enable/disable action.
    Message retention and the selected attachment bucket appear in the identity's
    details above the tabs. Organization quotas control message size and traffic.
  </Tab>

  <Tab title="API">
    Send `PATCH /v1/identities/{identity_id}/receiving`:

    ```json theme={null}
    {"enabled":true,"attachments_bucket":"incoming-email-files"}
    ```

    Send `POST /v1/identities/{identity_id}/recipient-rules`:

    ```json theme={null}
    {"match_type":"exact","local_part":"support","topic":"incoming-mail","enabled":true}
    ```

    This routes `support@example.com` when the identity is `example.com`.
    After configuring the rule, publish the MX record in the identity’s `dns_records` with `group: "receiving"`.
  </Tab>

  <Tab title="CLI">
    Save the JSON requests from the API tab, in order, as `request-1.json`, `request-2.json`. Set `IDENTITY_ID` to the resource UUID. Use your configured account and region.

    ```bash theme={null}
    basaltic email identity update-receiving "$IDENTITY_ID" --from-file request-1.json
    basaltic email recipient-rule create "$IDENTITY_ID" --from-file request-2.json
    ```
  </Tab>

  <Tab title="Go">
    Save the JSON requests from the API tab, in order, as `request-1.json`, `request-2.json`. Set `IDENTITY_ID` to the resource UUID. 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.UpdateReceivingConfigurationRequest
    	if err := readJSON("request-1.json", &request1); err != nil {
    		return err
    	}
    	if err := client.UpdateReceivingConfiguration(ctx, os.Getenv("IDENTITY_ID"), &request1); err != nil {
    		return err
    	}
    	var request2 email.CreateRecipientRuleRequest
    	if err := readJSON("request-2.json", &request2); err != nil {
    		return err
    	}
    	result2, err := client.CreateRecipientRule(ctx, os.Getenv("IDENTITY_ID"), &request2)
    	if err != nil {
    		return err
    	}
    	fmt.Printf("%+v\n", result2)
    	return nil
    }

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

Creating a rule requires `email:CreateRecipientRule` on the identity and
`notifications:Publish` on the destination topic. Topic resource-policy denies
also apply. Selecting a bucket delegates `storage:PutObject` and `storage:GetObject`
under `email/{identity_id}/` in that bucket. Current IAM permissions, permission
boundaries, bucket policies, and the original session scope-down remain enforced.
See [permissions](/email/permissions).

## Recipient matching

`match_type` makes the matching behavior explicit:

| Mode | `local_part` | Matches |
| - | - | - |
| `exact` | `support` | `support@example.com` only. |
| `prefix` | `reply+` | Addresses starting with `reply+`. |
| `all` | Omitted or empty | Any address at this identity. |

Exact rules take priority, followed by the longest matching prefix and then the
catch-all. Only enabled rules accept recipients. An unmatched recipient is rejected
during SMTP delivery. You can configure up to 100 rules per identity.

Rules have their own UUID and CRN. `POST` creates a rule and returns 409 when its
matching pattern already exists. Read or delete an individual rule at
`/v1/identities/{identity_id}/recipient-rules/{rule_id}`. Use `PATCH` on that path
to change only the supplied fields. List responses include normal `meta` pagination.
The `topic` response is a ResourceRef containing its UUID, CRN, and name.

Configuration PATCH also preserves omissions. Passing `attachments_bucket: null`
clears the binding only when receiving is disabled. To disable and detach together,
include both `enabled: false` and `attachments_bucket: null` in the same request.

## Read accepted mail

`GET /v1/received-messages` lists metadata across the account, with optional
`identity`, `recipient`, and `review_reason` filters. Read one record by UUID,
then request its `/content`, `/raw`, or `/attachments/{attachment_id}` separately.
`/content` returns full decoded text and HTML; `/raw` downloads reconstructed MIME.
Attachment IDs are stable opaque UUIDs. Metadata and content require separate grants.
Treat HTML as untrusted data and sanitize it before rendering.

`identity` is a ResourceRef. `rfc_message_id` in content is the RFC Message-ID header,
not the Email resource UUID. `notification_status` is `pending`, `retrying`, or
`published`; `notification_error` provides safe failure detail. Publication retries
automatically until expiry. These fields describe the notification event, not SMTP
acceptance, which happened before the message was returned.

`review_reason` can be `clear`, `malware`, `spam`, or `sender_unverified`.
Malware content is quarantined and cannot be downloaded through the content API.
Check `content_available` and `expires_at` before attempting retrieval.
`published_at` reports when the Notifications event was published, not when every
subscriber finished processing it.

The parsed content's `message_id`, `in_reply_to`, and `references` are RFC email
headers. They are not Basaltic resource UUIDs or ResourceRefs.

## Attachment storage

Each accepted attachment is stored as a separate object in the selected bucket.
Its metadata contains a `bucket` ResourceRef, `key`, optional `version_id`, and
`sha256`. Storage pricing, quotas, encryption, and lifecycle rules apply normally.
There is no additional Email storage allowance or accumulated attachment-storage cap.
A missing, changed, or deleted object is unavailable for download.

Email acknowledges an incoming SMTP delivery only after its attachments and the
message record are durable. Storage failures return a temporary SMTP failure so
the sender can retry. Retries do not charge a second received-message delivery.
Malware attachments are never written into the customer bucket.

Changing the bucket affects new messages; it does not move existing objects.
Disabling receiving preserves the bucket selection and previously accepted mail.
Clearing `attachments_bucket` with `null` requires receiving to be disabled.

## Quotas and retention

Sent and received message content is retained for **30 days**, included in its
sending or receiving price. `expires_at` identifies the deadline. Expired messages
are unavailable through the message APIs even if notification publication failed.
Background cleanup removes retained content; operational billing and deduplication
records can remain. Attachment objects are **not** deleted when an email expires:
the bucket owner controls their lifecycle and pays for their storage.

Organization quotas apply across accounts and identities in each region:

| Quota | Default | Meaning |
| - | - | - |
| `email/message_size_bytes` | 10 MiB | Complete encoded email, including attachments; configurable up to the 50 MiB service ceiling. |
| `email/received_recipients_per_second` | 100 | Accepted recipient deliveries in a rolling second; excess traffic receives a temporary SMTP failure. |
| `email/sent_recipients_per_second` | 28 | Accepted sending recipients in a rolling second. |
| `email/sent_recipients_per_day` | Unlimited | Accepted sending recipients in rolling 24 hours; an organization override can set a limit. |

There is no daily receiving allowance. One message accepted for three recipients
counts as three receiving deliveries. Duplicate retries are acknowledged without
counting again. SMTP rejects a message above the organization size quota; temporary
capacity or dependency failures ask the sender to retry.

Recipient matching supports 100 rules per domain. MIME parsing has technical
complexity bounds; these are separate from stored-byte capacity. The content API returns full text and HTML within the organization message-size
quota. Retrieve attachments from their bucket or the message attachment download
endpoint during retention.


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