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

# Send Quickstart

> Define an event, deploy it to an environment, publish it, and watch Repost deliver it, starting from an empty directory.

export const RepostHighlight = () => {
  useEffect(() => {
    const colors = {
      comment: ['#116329', '#6A9955'],
      constant: ['#0550AE', '#9CDCFE'],
      keyword: ['#CF222E', '#569CD6'],
      name: ['#953800', '#4EC9B0'],
      string: ['#0A3069', '#CE9178'],
      type: ['#0550AE', '#4EC9B0'],
      variable: ['#1F2328', '#9CDCFE']
    };
    const tokenPattern = /"[^"\n]*"|@[A-Za-z_]\w*|\b(?:String|Int|Float|Boolean|DateTime|Json)\b|\b(?:true|false)\b|\b\d+(?:\.\d+)?\b|\b(?:now|uuid|cuid)(?=\s*\()|\b[A-Z]\w*\b|\b[A-Za-z_]\w*\b/g;
    const append = (line, text, color) => {
      if (!text) return;
      if (!color) {
        line.append(text);
        return;
      }
      const span = document.createElement('span');
      span.textContent = text;
      span.style.color = color[0];
      span.style.setProperty('--shiki-dark', color[1]);
      line.append(span);
    };
    const commentStart = text => {
      let quoted = false;
      for (let index = 0; index < text.length - 1; index += 1) {
        if (text[index] === '"' && text[index - 1] !== '\\') quoted = !quoted;
        if (!quoted && text[index] === '/' && text[index + 1] === '/') return index;
      }
      return -1;
    };
    const highlightCode = code => {
      if (code.dataset.repostHighlighted !== undefined || code.querySelector('span[style*="--shiki-dark"]')) {
        return;
      }
      code.dataset.repostHighlighted = '';
      const source = code.textContent ?? '';
      const fragment = document.createDocumentFragment();
      for (const text of source.split('\n')) {
        const line = document.createElement('span');
        line.className = 'line';
        const declaration = text.match(/^(\s*)(generator|model|enum|type|event)(\s+)([A-Za-z_]\w*)(.*)$/);
        if (declaration) {
          append(line, declaration[1]);
          append(line, declaration[2], colors.keyword);
          append(line, declaration[3]);
          append(line, declaration[4], colors.name);
          append(line, declaration[5]);
        } else {
          const start = commentStart(text);
          const codeText = start === -1 ? text : text.slice(0, start);
          const leading = codeText.match(/^(\s*)([A-Za-z_]\w*)(?=\s+(?:@|[A-Z]))/);
          const bare = codeText.match(/^(\s*)([A-Za-z_]\w*)(\s*)$/);
          let offset = 0;
          for (const match of codeText.matchAll(tokenPattern)) {
            append(line, codeText.slice(offset, match.index));
            const token = match[0];
            let color = colors.constant;
            if (token.startsWith('"')) color = colors.string; else if (token.startsWith('@') || (/^(now|uuid|cuid)$/).test(token)) color = colors.name; else if ((/^(String|Int|Float|Boolean|DateTime|Json)$/).test(token)) color = colors.type; else if (leading && match.index === leading[1].length) color = colors.variable; else if (bare && match.index === bare[1].length) color = colors.constant; else if ((/^[A-Z]/).test(token)) color = colors.name; else if ((/^[A-Za-z_]/).test(token)) color = colors.variable;
            append(line, token, color);
            offset = match.index + token.length;
          }
          append(line, codeText.slice(offset));
          if (start !== -1) append(line, text.slice(start), colors.comment);
        }
        fragment.append(line, '\n');
      }
      code.replaceChildren(fragment);
    };
    const highlightRepostBlocks = () => {
      for (const block of document.querySelectorAll('.code-block')) {
        const code = block.querySelector('pre code');
        const filename = block.querySelector('[data-component-part="code-block-header-filename"] [title$=".repost"]');
        const looksLikeRepost = (/^\s*(generator|model|enum|type|event)\s+[A-Za-z_]\w*\s*\{/m).test(code?.textContent ?? '');
        if (code && (filename || looksLikeRepost)) highlightCode(code);
      }
    };
    let frame;
    const schedule = () => {
      if (frame !== undefined) return;
      frame = requestAnimationFrame(() => {
        frame = undefined;
        highlightRepostBlocks();
      });
    };
    const observer = new MutationObserver(schedule);
    observer.observe(document.documentElement, {
      childList: true,
      subtree: true
    });
    schedule();
    return () => {
      observer.disconnect();
      if (frame !== undefined) cancelAnimationFrame(frame);
    };
  }, []);
  return null;
};

<RepostHighlight />

This guide walks the whole system once: define an event in a schema, deploy it to an environment, publish it, watch it delivered, and give a customer their portal.

You need the `repost` CLI, a [Repost account](https://app.repost.sh), and `curl`. No SDK is required for the walkthrough; the last step shows where the typed clients pick up.

<Steps>
  <Step title="Install the CLI and sign in">
    The CLI is a single `repost` binary with the schema engine built in.

    <CodeGroup>
      ```bash macOS/Linux theme={"languages":{"custom":["/languages/repost.json"]}}
      curl -fsSL https://releases.repost.sh/cli/install.sh | sh
      repost auth login
      ```

      ```powershell Windows theme={"languages":{"custom":["/languages/repost.json"]}}
      irm https://releases.repost.sh/cli/install.ps1 | iex
      repost auth login
      ```
    </CodeGroup>
  </Step>

  <Step title="Create a schema workspace">
    ```bash theme={"languages":{"custom":["/languages/repost.json"]}}
    repost schema init
    ```

    `init` asks which SDK language to generate and where the output should go, then scaffolds a `repost/` directory:

    ```text theme={"languages":{"custom":["/languages/repost.json"]}}
    repost/
    ├── schema.repost   # your event definitions; starts with an example
    ├── .env            # REPOST_SEND_API_KEY and REPOST_TOKEN, empty for now
    └── .gitignore      # keeps .env out of your repo
    ```

    You'll fill in the two `.env` values in step 5.
  </Step>

  <Step title="Define your first event">
    Open `repost/schema.repost`. A schema has three parts: a payload model, an event-type catalog, and an event that ties them together.

    ```repost repost/schema.repost theme={"languages":{"custom":["/languages/repost.json"]}}
    model Order {
      id       String
      currency String
    }

    type Order {
      created
    }

    event OrderCreated {
      type      @type(Order.created)
      data      Order
      timestamp DateTime
    }
    ```

    This defines one event, `order.created`, carrying an `Order` payload and a timestamp. The [schema language](/docs/send/schema) page covers every construct.
  </Step>

  <Step title="Version it and generate the client">
    ```bash theme={"languages":{"custom":["/languages/repost.json"]}}
    repost schema migrate dev --name init
    ```

    `migrate dev` records the schema as a migration (committed to your repo and reviewable in `git diff`, like a database migration) and generates the typed client for the language you picked.
  </Step>

  <Step title="Create an environment and a token">
    In the [dashboard](https://app.repost.sh), create an environment. This is where your events go live, with its own registry, customers, and keys. Then open **Settings → API Tokens** inside it and create a token.

    A token carries one or more scopes:

    | Scope           | What it allows                                  |
    | --------------- | ----------------------------------------------- |
    | `publish`       | Publish messages into this environment.         |
    | `manage`        | Manage customers and endpoints through the API. |
    | `schema:deploy` | Deploy schemas from the CLI.                    |

    Select all three for this walkthrough. In production, give each system its own narrowly scoped token. The token value starts with `rp_` and is shown once. Copy it into `repost/.env` as both values:

    ```bash repost/.env theme={"languages":{"custom":["/languages/repost.json"]}}
    REPOST_SEND_API_KEY="rp_..."
    REPOST_TOKEN="rp_..."
    ```
  </Step>

  <Step title="Deploy your events">
    ```bash theme={"languages":{"custom":["/languages/repost.json"]}}
    repost schema migrate deploy
    ```

    The CLI sends your committed migrations to the environment and prints the applied diff. `order.created` is now a live, versioned event type, visible under **Event Types** in the dashboard.
  </Step>

  <Step title="Add a customer and an endpoint">
    A customer is who you send to, identified by an `externalId` you choose. An endpoint is where their webhooks go. Creating an endpoint also creates the customer:

    ```bash theme={"languages":{"custom":["/languages/repost.json"]}}
    curl -X POST https://api.repost.sh/v1/customers/acme/endpoints \
      -H "Authorization: Bearer $REPOST_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"url": "https://acme.example.com/webhooks"}'
    ```

    The `201` response contains the endpoint and its `whsec_...` signing secret, which is returned only this once. By default the endpoint subscribes to every event type (`"subscriptions": ["*"]`).

    <Tip>
      No receiver handy? Create a [bucket](/docs/buckets/create) in the Ingest tab and use its URL. The delivered webhook, signed headers included, will show up in the bucket's event history.
    </Tip>
  </Step>

  <Step title="Publish your first event">
    ```bash theme={"languages":{"custom":["/languages/repost.json"]}}
    curl -X POST https://api.repost.sh/v1/messages \
      -H "Authorization: Bearer $REPOST_SEND_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: order-1001" \
      -d '{
        "type": "order.created",
        "customerId": "acme",
        "data": { "id": "order-1001", "currency": "EUR" }
      }'
    ```

    ```json 202 Accepted theme={"languages":{"custom":["/languages/repost.json"]}}
    {
      "id": "msg_01JZX3NV7Q9WD9F2M6K4T8RSAB",
      "type": "order.created",
      "customerId": "acme",
      "timestamp": "2026-07-17T12:00:00.000Z"
    }
    ```

    Run it twice. The second call returns the same message with an `Idempotent-Replay: true` header instead of publishing a duplicate. [Idempotency](/docs/send/publishing#idempotency) works this way for every publish.
  </Step>

  <Step title="Watch it deliver">
    Open **Messages** in the environment to follow the message to its delivery, attempt by attempt. On the receiving end, the webhook arrives as a signed Standard Webhooks request:

    ```http theme={"languages":{"custom":["/languages/repost.json"]}}
    POST /webhooks HTTP/1.1
    content-type: application/json
    user-agent: Repost-Webhooks/1
    webhook-id: msg_01JZX3NV7Q9WD9F2M6K4T8RSAB
    webhook-timestamp: 1784289600
    webhook-signature: v1,K5oZfzN95Z9UVu1EsPQqUIoRQOU...

    {"type":"order.created","timestamp":"2026-07-17T12:00:00.000Z","data":{"id":"order-1001","currency":"EUR"}}
    ```

    If the endpoint is down, Repost [retries for about a day](/docs/send/delivery) before parking the delivery in the dead-letter queue.
  </Step>

  <Step title="Give your customer the portal">
    Customers manage their own endpoints, secrets, and delivery logs in a hosted portal. Mint an access link:

    ```bash theme={"languages":{"custom":["/languages/repost.json"]}}
    curl -X POST https://api.repost.sh/v1/customers/acme/portal-access \
      -H "Authorization: Bearer $REPOST_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    Open the returned `url`. This is the [customer portal](/docs/send/portal), with your [branding](/docs/send/customization), and no customer account is required.
  </Step>
</Steps>

## Where to go next

<Columns cols={2} className="gap-y-4">
  <Card title="Defining events" icon="braces" href="/docs/send/schema" cta="Schema" arrow="true">
    Models, event types, defaults, and what each line generates.
  </Card>

  <Card title="SDKs" icon="package" href="/docs/send/sdks" cta="Typed clients" arrow="true">
    Replace the `curl` with a generated client in your language.
  </Card>

  <Card title="Delivery & retries" icon="shield-check" href="/docs/send/delivery" cta="Reliability" arrow="true">
    The retry schedule, the dead-letter queue, and replay.
  </Card>

  <Card title="Customization" icon="palette" href="/docs/send/customization" cta="Branding" arrow="true">
    Put your name and theme on the portal and event docs.
  </Card>
</Columns>
