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

# TypeScript Quickstart

> Install the runtime, generate a typed client into node_modules, and publish your first event from Node.js.

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 />

The TypeScript client is generated into `node_modules`, and the `@repost/client` package re-exports it, the same way Prisma's client works. Your application imports one stable package; generation fills it with types from your schema.

Node.js 20 or later is required.

<Steps>
  <Step title="Install the CLI and the runtime">
    <CodeGroup>
      ```bash npm theme={"languages":{"custom":["/languages/repost.json"]}}
      npm install -g @repost/cli
      npm install @repost/client
      ```

      ```bash Install script theme={"languages":{"custom":["/languages/repost.json"]}}
      curl -fsSL https://repost.sh/install | sh
      npm install @repost/client
      ```
    </CodeGroup>
  </Step>

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

    TypeScript is the default language, so no flags are needed. This scaffolds `repost/schema.repost` with a starter `user.created` event and a `.env` file with an empty `REPOST_SEND_API_KEY`:

    ```repost repost/schema.repost theme={"languages":{"custom":["/languages/repost.json"]}}
    generator sdk {
      language = "typescript"
    }

    model User {
      id    String
      email String
    }

    type User {
      created
    }

    event UserCreated {
      type      @type(User.created)
      data      User
      timestamp DateTime
    }
    ```

    With no `output` in the generator block, generation targets `node_modules/.repost/client`, which `@repost/client` re-exports.
  </Step>

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

    This records your schema as a migration and generates the client. Until a first generate has run, importing `@repost/client` fails with:

    ```text theme={"languages":{"custom":["/languages/repost.json"]}}
    @repost/client did not initialize yet. Please run "repost schema generate" and try to import it again.
    ```
  </Step>

  <Step title="Connect an environment">
    Create an environment in the [dashboard](https://app.repost.sh), copy its publish API key into `.env`, and deploy your schema:

    ```bash theme={"languages":{"custom":["/languages/repost.json"]}}
    repost auth login
    repost schema migrate deploy
    ```

    Load `.env` through your framework, your process manager, or Node's `--env-file` option.
  </Step>
</Steps>

## Your first send

```ts theme={"languages":{"custom":["/languages/repost.json"]}}
import { createRepostClient, type User } from "@repost/client";

const repost = createRepostClient();

const user: User = {
  id: "user_123",
  email: "ada@example.com",
};

const result = await repost.webhooks.user.created({
  customerId: "customer_123",
  data: user,
  idempotencyKey: "user_123:created",
});

console.log(result.id); // msg_...
```

`createRepostClient()` never throws; the API key resolves from `REPOST_SEND_API_KEY` at send time, so a missing key surfaces on the first send. `customerId` names the customer receiving the event. The `idempotencyKey` is optional; reusing it with the same payload is safe, and [Reliability](/docs/send/typescript/reliability) explains when to pass your own.

TypeScript rejects unknown events, missing fields, and incompatible values before the send ever reaches Repost.

## Continue

<Columns cols={3} className="gap-y-4">
  <Card title="Code generation" icon="cog" href="/docs/send/typescript/generation" cta="Generation" arrow="true">
    What lands in `node_modules`, and how regeneration works.
  </Card>

  <Card title="Configuration" icon="sliders-horizontal" href="/docs/send/typescript/configuration" cta="Configure" arrow="true">
    Client options, transport tuning, and injectable generators.
  </Card>

  <Card title="Reliability" icon="shield-check" href="/docs/send/typescript/reliability" cta="Errors" arrow="true">
    Retries, idempotency, and the error surface.
  </Card>
</Columns>
