Files
2026-02-07 13:04:04 +01:00
..
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00
2026-02-07 13:04:04 +01:00

Skrift Backend - Handwritten Document Generator

Docker-basiertes Backend für die Generierung von handschriftlichen Dokumenten (Briefe, Postkarten, Umschläge) mit SVG-Output.

Features

  • Preview-System: Batch-Generierung von Vorschauen mit Caching (30 Briefe pro Batch)
  • Scriptalizer Integration: Nutzt externe API für natürliche Handschrift-Variationen
  • SVG-Generierung: Eigene Font-Engine für hochqualitative SVG-Ausgabe
  • Multi-Format Support: A4, A6 (Hoch-/Querformat), C6, DIN Lang Umschläge
  • Platzhalter-System: Automatische Ersetzung von [[Platzhalter]] mit CSV-Export
  • Rate Limiting: Schutz vor API-Spam (konfigurierbar)
  • Docker-Ready: Vollständig containerisiert mit docker-compose

Quick Start

1. Konfiguration

cp .env.example .env
# .env bearbeiten und Scriptalizer License Key eintragen

2. Lokal testen (ohne Docker)

npm install
npm start

Server läuft auf: http://localhost:4000

3. Mit Docker deployen

docker-compose up -d

API-Endpunkte

Health Check

GET /health

Response:

{
  "status": "healthy",
  "timestamp": "2026-01-15T12:00:00Z",
  "uptime": 12345,
  "scriptalizer": "configured",
  "storage": {
    "cache": true,
    "output": true
  }
}

Preview Batch Generierung

POST /api/preview/batch

Request Body:

{
  "sessionId": "uuid-abc-123",
  "batchIndex": 0,
  "forceRegenerate": false,
  "config": {
    "font": "tilda",
    "letters": [
      {
        "index": 0,
        "format": "a4",
        "text": "Hallo [[Vorname]], dein Code ist [[Gutscheincode]]...",
        "placeholders": {
          "Vorname": "Max",
          "Nachname": "Mustermann",
          "Strasse": "Hauptstr. 1",
          "PLZ": "10115",
          "Ort": "Berlin",
          "Gutscheincode": "SAVE20"
        }
      }
    ],
    "envelopes": [
      {
        "index": 0,
        "format": "c6",
        "type": "recipient",
        "data": {
          "Vorname": "Max",
          "Nachname": "Mustermann",
          "Strasse": "Hauptstr. 1",
          "PLZ": "10115",
          "Ort": "Berlin"
        }
      }
    ]
  }
}

Response:

{
  "sessionId": "uuid-abc-123",
  "files": [
    {
      "type": "letter",
      "index": 0,
      "url": "/api/preview/uuid-abc-123/letter_000.svg"
    },
    {
      "type": "envelope",
      "index": 0,
      "url": "/api/preview/uuid-abc-123/envelope_000.svg"
    }
  ],
  "csvUrl": "/api/preview/uuid-abc-123/platzhalter.csv",
  "expiresAt": "2026-01-15T14:00:00Z"
}

Rate Limit: 2 Requests/Minute pro sessionId


Preview-Datei abrufen

GET /api/preview/:sessionId/:filename

Beispiel:

GET /api/preview/uuid-abc-123/letter_000.svg

Response: SVG-Datei (Content-Type: image/svg+xml)


Bestellung finalisieren (aus Cache)

POST /api/order/finalize

Request Body:

{
  "sessionId": "uuid-abc-123",
  "orderNumber": "SK-2026-01-15-001"
}

Response:

{
  "orderNumber": "SK-2026-01-15-001",
  "outputPath": "/app/output/SK-2026-01-15-001",
  "files": {
    "letters": 100,
    "envelopes": 100,
    "csv": "platzhalter.csv"
  },
  "timestamp": "2026-01-15T12:30:00Z"
}

Bestellung neu generieren (ohne Cache)

POST /api/order/generate

Request Body:

{
  "orderNumber": "SK-2026-01-15-002",
  "config": {
    "font": "tilda",
    "letters": [...],
    "envelopes": [...]
  }
}

Response: Gleich wie /api/order/finalize


Formate

Schriftstücke (Letters)

  • a4 - A4 Hochformat (210 × 297 mm)
  • a6p - A6 Hochformat (105 × 148 mm)
  • a6l - A6 Querformat (148 × 105 mm)

Umschläge (Envelopes)

  • c6 - C6 Umschlag (162 × 114 mm)
  • din_lang - DIN Lang Umschlag (220 × 110 mm)

Fonts

  • tilda - PremiumUltra79
  • alva - PremiumUltra23
  • ellie - PremiumUltra39

Umschlag-Typen

Empfänger-Adresse (type: "recipient")

Adresse wird unten links positioniert (kein Sichtfenster).

{
  "type": "recipient",
  "data": {
    "Vorname": "Max",
    "Nachname": "Mustermann",
    "Strasse": "Hauptstr. 1",
    "PLZ": "10115",
    "Ort": "Berlin"
  }
}

Individueller Text (type: "custom")

Text wird mittig zentriert positioniert. Max. 150 Zeichen.

{
  "type": "custom",
  "data": {
    "customText": "Für meine großartige Freundin Caro"
  }
}

Verzeichnisstruktur

/app/
├── cache/
│   └── previews/
│       └── {sessionId}/
│           ├── letter_000.svg
│           ├── envelope_000.svg
│           ├── platzhalter.csv
│           └── metadata.json
│
├── output/
│   └── {orderNumber}/
│       ├── schriftstuecke/
│       │   ├── brief_000.svg
│       │   └── ...
│       ├── umschlaege/
│       │   ├── umschlag_000.svg
│       │   └── ...
│       ├── platzhalter.csv
│       └── order-metadata.json
│
└── fonts/
    ├── tilda.svg
    ├── alva.svg
    └── ellie.svg

Umgebungsvariablen

# Node Environment
NODE_ENV=production

# Scriptalizer API
SCRIPTALIZER_LICENSE_KEY=your-key-here
SCRIPTALIZER_ERR_FREQUENCY=10

# Preview System
BATCH_SIZE=30
CACHE_LIFETIME_HOURS=2
RATE_LIMIT_PER_MINUTE=2

# Server
PORT=4000
CORS_ORIGIN=*

Deployment

Auf Server (mit Docker)

# .env Datei erstellen mit production values
docker-compose up -d

# Logs ansehen
docker-compose logs -f

# Stoppen
docker-compose down

Nginx Proxy Manager Setup

  1. Proxy Host erstellen
  2. Domain: api.skrift.de (oder deine Domain)
  3. Forward Hostname/IP: localhost
  4. Forward Port: 4000
  5. SSL Zertifikat über NPM erstellen

Entwicklung

Lokales Testen

npm run dev  # Mit nodemon

Scriptalizer Separator Test

npm run test:separator

Logs

# Docker logs
docker-compose logs -f skrift-backend

# Lokale logs
# Output in console

Integration mit N8N

N8N kann direkt auf den /app/output/{orderNumber}/ Ordner zugreifen:

// N8N Workflow (Beispiel)
const fs = require('fs');
const orderPath = '/var/skrift-output/SK-2026-01-15-001';

// Lese alle SVGs
const letters = fs.readdirSync(`${orderPath}/schriftstuecke`);

// Sende an Plotter
for (const file of letters) {
  await sendToPlotter(`${orderPath}/schriftstuecke/${file}`);
}

Fehlerbehandlung

HTTP Status Codes

  • 200 - Success
  • 400 - Bad Request (z.B. ungültige Parameter)
  • 404 - Not Found (z.B. Session nicht gefunden)
  • 410 - Gone (z.B. Cache abgelaufen)
  • 429 - Too Many Requests (Rate Limit)
  • 500 - Internal Server Error
  • 503 - Service Unavailable (z.B. Scriptalizer down)

Typische Fehler

Rate Limit überschritten:

{
  "error": "Zu viele Vorschau-Anfragen. Bitte warten Sie.",
  "retryAfter": 45,
  "message": "Limit: 2 Anfragen pro Minute"
}

Scriptalizer Fehler:

{
  "error": "Scriptalizer request failed: timeout"
}

Cache abgelaufen:

{
  "error": "Preview-Session abgelaufen. Bitte neu generieren."
}

Limits

  • Scriptalizer API: 10.000 Calls/Tag
  • Batch Size: 30 Briefe pro Request
  • Input Size: 48KB pro Scriptalizer Call
  • Rate Limit: 2 Preview-Requests/Minute
  • Cache Lifetime: 2 Stunden

Troubleshooting

Fonts nicht gefunden

# Fonts kopieren
cp /path/to/fonts/*.svg ./fonts/

Scriptalizer API Fehler

# License Key prüfen
cat .env | grep SCRIPTALIZER_LICENSE_KEY

# Test-Script ausführen
npm run test:separator

Permissions Fehler

# Cache/Output Ordner Permissions
chmod -R 755 cache output

Weitere Infos


Version: 1.0.0 Last Updated: 2026-01-01