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

# Publishing messages

> Publish attributed notifications, choose protocol-specific payloads, and inspect fan-out.

Publish to a topic after creating its subscriptions. The publish response means
the notification was accepted for asynchronous fan-out. It does not promise
that a recipient has received or processed it.

## Publish a notification

<Tabs>
  <Tab title="Console">
    Open the topic and select **Publish**. Enter the message and optional
    subject and attributes. After publishing, the console opens the message
    page. Use the topic's **Messages** and **Deliveries** tabs to return to it.
  </Tab>

  <Tab title="API">
    ```http theme={null}
    POST /v1/topics/{topic_id}/publish
    Content-Type: application/json
    ```

    ```json theme={null}
    {
      "subject": "Order paid",
      "message": "{\"order_id\":\"A-1001\",\"amount\":149.50}",
      "attributes": {
        "event": { "type": "String", "value": "order.paid" },
        "environment": { "type": "String", "value": "production" },
        "amount": { "type": "Number", "value": "149.50" }
      }
    }
    ```

    HTTP `202` returns `{"message_id": "..."}`. Save the ID to inspect
    `/v1/topics/{topic_id}/messages/{message_id}`. Listing messages uses
    `/v1/topics/{topic_id}/messages` with marker pagination.
  </Tab>

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

    ```bash theme={null}
    basaltic notifications topic publish "$TOPIC_ID" --from-file request-1.json
    ```
  </Tab>

  <Tab title="Go">
    Save the JSON request from the API tab as `request-1.json`. Set `TOPIC_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/notifications"
    )

    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 := notifications.New(cfg)
    	var request1 notifications.PublishRequest
    	if err := readJSON("request-1.json", &request1); err != nil {
    		return err
    	}
    	result1, err := client.Publish(ctx, os.Getenv("TOPIC_ID"), &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>

## Payloads and attributes

`message` is a nonempty string, up to 256 KiB. JSON application payloads must
be encoded as strings in that field. The optional subject is at most 100 bytes
and cannot contain line breaks. Attributes are a separate map used for
[subscription filtering](/notifications/filter-policies).

By default the same message is used for every protocol. To choose different
payloads, set `message_structure` to `json` and encode an object of string
values inside `message`. A `default` value is required:

```json theme={null}
{
  "message_structure": "json",
  "message": "{\"default\":\"Order A-1001 paid\",\"email\":\"Your order A-1001 is paid.\",\"https\":\"{\\\"order_id\\\":\\\"A-1001\\\"}\"}"
}
```

Each subscription selects its protocol key, or `default` if there is no key
for that protocol. This selection does not change filtering: filters still
read the publication's attributes.

## Batch publishing

The publish-batch operation accepts 1–10 entries with unique caller-chosen
entry IDs. Its response separates successful entries from failures. Inspect
each result rather than treating an HTTP success as proof that every entry
was accepted. Retry only the entries you intend to publish again; a fresh
accepted publication has a new message ID.

## Follow delivery

A message progresses through fan-out states `pending`, `complete`, or `failed`.
A completed fan-out with zero deliveries can be valid: there may be no
confirmed subscriptions, or all filter policies may reject the attributes.

Delivery records show the subscription reference, outcome, attempts, and last
error. Listing them under the topic accepts a `message` UUID or CRN to narrow
the result. Follow pagination markers rather than assuming the first page is
complete. The **Metrics** tab shows trends through Telemetry; use delivery
records to investigate an individual failure.

Webhook and queue consumers should handle repeated delivery safely. A
successful webhook attempt means an HTTP success response; a successful queue
attempt means the notification was enqueued, not that the queue consumer has
processed it. See [delivery behavior](/notifications/delivery).


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