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

# Automatisierungen

> Planen Sie Jobs und reagieren Sie auf Webhooks aus einer Kazzle-App.

Automatisierungen sind die Teile einer App, die eigenständig laufen — nach einem Zeitplan oder wenn ein externer Service ein Ereignis sendet.

Sie deklarieren sie in einer Process-Komponente in `kazzle.config.ts`. Eine Komponente kann beliebig viele Trigger haben.

## Die Struktur

```ts theme={"theme":"material-theme-darker"}
{
  name: 'events',
  type: 'process',
  path: './components/events/index.ts',
  processMode: 'persistent', // oder 'triggered'
  triggers: [
    { name: 'daily-digest', kind: 'schedule', schedule: '0 9 * * *', path: '/cron/daily-digest' },
    { name: 'stripe',       kind: 'webhook',                          path: '/webhook/stripe' },
  ],
}
```

Zwei Dinge passieren hier:

* `processMode` bestimmt den **Lebenszyklus** — Langzeitserver oder einmalige Ausführung pro Trigger.
* `triggers[]` listet die **Ereignisse** auf, die diese Komponente auslösen sollen.

Die beiden Teile sind unabhängig. Ein persistenter Server kann einen Cron haben. Ein kurzlebiger Prozess kann einen Webhook haben. Wählen Sie den Lebenszyklus, der zu Ihrer Workload passt, und fügen Sie dann beliebig viele Trigger hinzu.

## `processMode`

| Modus                   | Was läuft                                                | Wann verwenden                                                                                                 |
| ----------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `persistent` (Standard) | Ein Langzeit-HTTP-Server. Trigger werden darin gepostet. | Die Komponente bedient bereits HTTP oder hält Zustand im Speicher (Warteschlangen, Websockets, Caches).        |
| `triggered`             | Das Entry-Skript wird pro Trigger gestartet und beendet. | Reine Hintergrund-Jobs — nächtliche Bereinigung, einzelner Stripe-Webhook-Handler usw. Keine untätigen Server. |

## Trigger

Jeder Trigger hat einen `name` (eindeutig innerhalb der Komponente), eine `kind` und — je nach Modus — einen `schedule` und/oder `path`.

| Feld       | Erforderlich wenn                | Hinweise                                                        |
| ---------- | -------------------------------- | --------------------------------------------------------------- |
| `name`     | immer                            | Wird als Webhook-URL-Segment und in Logs verwendet. Kebab-Case. |
| `kind`     | immer                            | `'schedule'` oder `'webhook'`.                                  |
| `schedule` | wenn `kind: 'schedule'`          | 5-Feld-Cron-Ausdruck. Minutenauflösung ist das Minimum.         |
| `path`     | wenn `processMode: 'persistent'` | HTTP-Route auf Ihrem Server, wo der Trigger ankommt.            |

## Persistent-Modus — HTTP zum Server

Wenn ein Trigger für eine persistente Komponente ausgelöst wird, postet Kazzle zu Ihrem Server unter dem deklarierten `path`. Die Anfrage enthält:

| Header                                           | Was er aussagt                                                        |
| ------------------------------------------------ | --------------------------------------------------------------------- |
| `Authorization: Bearer ${KAZZLE_TRIGGER_SECRET}` | Validieren Sie dies. Lehnen Sie Aufrufe ab, die nicht übereinstimmen. |
| `x-kazzle-trigger-name`                          | Der `name` des Triggers aus dem Manifest.                             |
| `x-kazzle-trigger-run-id`                        | Opaque ID für Log-Korrelation.                                        |
| `x-kazzle-triggered-by`                          | `cron` \| `webhook` \| `manual`.                                      |

Bei Webhook-Triggern wird der ursprüngliche Request-Body als POST-Body weitergeleitet. Bei Schedule-Triggern ist der Body leer.

```ts theme={"theme":"material-theme-darker"}
// components/events/index.ts (persistent mode)
const TRIGGER_SECRET = process.env.KAZZLE_TRIGGER_SECRET ?? '';

Bun.serve({
  port: Number(process.env.PORT),
  hostname: process.env.HOST,
  async fetch(req) {
    const url = new URL(req.url);

    if (req.method === 'POST' && url.pathname === '/cron/daily-digest') {
      if (req.headers.get('authorization') !== `Bearer ${TRIGGER_SECRET}`) {
        return new Response('Unauthorized', { status: 401 });
      }
      await sendDigest();
      return Response.json({ ok: true });
    }

    if (req.method === 'POST' && url.pathname === '/webhook/stripe') {
      if (req.headers.get('authorization') !== `Bearer ${TRIGGER_SECRET}`) {
        return new Response('Unauthorized', { status: 401 });
      }
      const event = await req.json();
      await handleStripe(event);
      return Response.json({ ok: true });
    }

    return new Response('not found', { status: 404 });
  },
});
```

## Triggered-Modus — einmalig pro Trigger

Wenn ein Trigger für eine `triggered`-Komponente ausgelöst wird, startet Kazzle das Entry-Skript neu und wartet auf seinen Abschluss. Es gibt keinen `path`; das Skript erfährt, welcher Trigger ausgelöst wurde, über Umgebungsvariablen.

| Umgebungsvariable | Wert                                      |
| ----------------- | ----------------------------------------- |
| `TRIGGER_NAME`    | Der `name` des Triggers aus dem Manifest. |
| `TRIGGERED_BY`    | `cron` \| `webhook` \| `manual`.          |
| `RUN_ID`          | Opaque ID für Log-Korrelation.            |
| `WEBHOOK_PAYLOAD` | JSON-Body (nur Webhook-Trigger).          |

```ts theme={"theme":"material-theme-darker"}
// components/events/index.ts (triggered mode)
const trigger = process.env.TRIGGER_NAME;
const runId = process.env.RUN_ID;

if (trigger === 'daily-digest') {
  await sendDigest();
} else if (trigger === 'stripe') {
  const event = JSON.parse(process.env.WEBHOOK_PAYLOAD ?? '{}');
  await handleStripe(event);
}

console.log(`run ${runId} done`);
```

Triggered-Komponenten haben keine untätigen Maschinen in der Produktion — sie starten pro Aufruf und fahren beim Beenden herunter.

## Webhook-URLs

```text theme={"theme":"material-theme-darker"}
POST https://api.kazzle.app/webhooks/{spaceId}/{appId}/{componentName}/{triggerName}
```

Das `triggerName`-Segment muss einem `kind: 'webhook'`-Eintrag in der `triggers[]` dieser Komponente entsprechen. Unbekannte Trigger-Namen geben 404 zurück.

## Zeitplan-Auflösung

Cron-Ausdrücke sind 5-Feld (Minute, Stunde, Tag des Monats, Monat, Wochentag) und Minutenauflösung ist das Minimum. Sub-Minuten-Zeitpläne werden bei der Manifest-Validierung abgelehnt.

## Wie Läufe aufgezeichnet werden

Jeder Trigger-Auslöser schreibt eine `process_runs`-Zeile mit `trigger_name`, `triggered_by`, `run_id` und dem Exit-Status des Laufs. Sie können diese aus Ihrem eigenen Code abfragen oder in der Runs-Ansicht der App inspizieren.

## Guthaben aufgebraucht

Ein fehlgeschlagener Lauf wird aufgezeichnet und protokolliert, aber der Zeitplan läuft weiter nach seinem normalen Rhythmus — ein fehlerhafter Lauf deaktiviert den Trigger nie. Das Einzige, das einen Lauf stoppt, ist das Guthaben: Jeder Trigger-Auslöser wird gegen den Kontostand des Space überprüft, und während der Space kein Guthaben hat (oder keine Abrechnung eingerichtet hat), werden Läufe mit `402` übersprungen. Dies ist selbstheilend — der Zeitplan bleibt aktiv und der nächste Auslöser nach dem Aufladen läuft normal, ohne manuelles Fortsetzen.

## Automatisierungen später hinzufügen

Eine einfache App kann ohne Trigger starten und sie später gewinnen — fügen Sie eine tägliche Zusammenfassung hinzu, verbinden Sie Stripe, führen Sie Bereinigung aus. Der Lebenszyklus der Komponente (`processMode`) und Trigger (`triggers[]`) sind unabhängig, sodass Sie sie ändern können, ohne den Rest der App umzuschreiben.
