Changelog
Alle nennenswerten Änderungen an PostOmnia.
Changelog
Alle nennenswerten Änderungen an diesem Projekt werden hier dokumentiert.
Format angelehnt an Keep a Changelog.
[Unveröffentlicht]
Verkaufsreife (Analyse in docs/VERKAUFSREIFE.md)
- Die Auslieferungsmarke wurde 68 Deploys lang nicht gesetzt.
git tag -a
schreibt einen Tagger ins Tag-Objekt und scheitert ohne Git-Identität mit
„Committer identity unknown"; auf dem Actions-Runner ist keine gesetzt, und
actions/checkout setzt auch keine. Der Deploy-Workflow konfiguriert jetzt die
Identität des Actions-Bots, bevor das Skript läuft. Warum es so lange unentdeckt
blieb, war der zweite Teil des Fehlers: git tag -a … 2>/dev/null verschluckte
den Grund und meldete nur „konnte nicht gesetzt werden". Beide Fehlerpfade der
Marke (Tag und Push) nennen jetzt die tatsächliche Meldung. Ohne Tag ließ sich
nicht sagen, welcher Stand wann live ging — der Rollback selbst war nie
betroffen, der hängt am Zeitstempel der Server-Sicherung.
- Zurück auf Bio Free — in der Abrechnung, nicht nur bei Stripe. Bio Free war
aus der Tarifliste ausgeblendet, solange ein bezahlter Tarif lief; der einzige
Weg heraus führte über das Stripe-Kundenportal. Die Tarifkarte steht jetzt
jedem Workspace offen und führt über POST /billing/downgrade.
Ein laufendes Stripe-Abo endet dabei zum Ende der bezahlten Periode — die
Periode ist bezahlt und darf genutzt werden —, alles andere (manueller
Provider, offene Zahlung, bereits gekündigt) wechselt sofort. Bestätigt wird
vorher mit dem, was der Wechsel kostet: verbundene Kanäle bleiben, geplante
Beiträge laufen aus, neue Beiträge, Kanäle, Teammitglieder, KI-Credits und
Analytics entfallen, die Link-in-Bio-Seite bleibt.
- Ein beendeter Vertrag endet auf Bio Free, nicht auf Creator. Jede beendete
Stripe-Subscription fiel bisher auf Creator zurück — einen bezahlten Tarif.
Wer kündigte, behielt 30 Kanäle, Team und KI-Credits, ohne weiter zu zahlen,
und ein erklärter Wechsel auf Bio Free wäre am Periodenende still ein Wechsel
auf Creator geworden. Bio Free ist der Tarif ohne Vertrag; genau dorthin gehört
ein Team, dessen Vertrag ausgelaufen ist. Der verbrauchte Trial bleibt
verbraucht: Bio Free ist sonst der Weg zu beliebig vielen Testphasen.
- Was diese Umstellung in der Auswertung verdeckt hätte: Gekündigte Teams
waren im Ober-Admin am Status canceled erkennbar. Auf Bio Free ist der Status
wieder active — die Kündigung wäre als „Free-Kunde" verbucht worden und die
Abwanderung unsichtbar. traction.customers() zählt Kündigungen jetzt an
canceledAt, nicht am Status; die Churn-Auswertung tat das ohnehin schon.
Bestandsteams, die durch eine frühere Kündigung auf Creator stehen, bleiben
unangetastet — das ist eine Korrektur an Kundendaten, keine Codeänderung.
- Der AVV ließ sich nicht abschließen, obwohl der Vertrag es zusagte. Der
Vertragstext verweist für die Annahme auf „Einstellungen → Datenschutz →
Auftragsverarbeitung"; im Dashboard gab es diesen Bereich nicht. Status und
Annahme existierten nur als REST-Route (GET/POST /teams/current/dpa) —
erreichbar für Skripte, nicht für den Kunden. Der Datenschutz-Bereich zeigt
jetzt Fassung, Stand, Annahmestatus und die drei Vertragsdokumente; Inhaber
und Admins nehmen nach ausdrücklicher Bestätigung an, Mitglieder sehen den
Status samt Hinweis, wer annehmen darf.
- Nachweis der Annahme (
GET /teams/current/dpa/record): druckbarer Beleg
mit Verantwortlichem, Auftragsverarbeiter, angenommener Fassung, Zeitpunkt und
handelnder Person — das zweite Versprechen des Vertragstexts („die
unterzeichnete Fassung mit Datum zum Download"), das bis dahin ebenfalls
unerfüllt war. Fassung und Stand kommen aus dem Annahmeprotokoll, nicht aus
der aktuellen Konstante: Der Beleg dokumentiert, was angenommen wurde, und
weist auf eine seither erschienene neuere Fassung hin.
- Lizenz- und Markenfrage aufgearbeitet (
docs/LIZENZ-UND-MARKE.md): geprüfte
Fakten, Optionen mit Empfehlung, fertiger Prüfauftrag an den Anwalt. Geprüft:
ein einziger Rechteinhaber (61 Commits, keine fremden Beitragenden), 118
Abhängigkeiten sämtlich permissiv (kein Copyleft, keine unbekannte Lizenz).
LICENSEnannte einen Platzhalter als Rechteinhaber (`[DEIN NAME /
DEINE FIRMA]`). Eine MIT-Lizenz verpflichtet Lizenznehmer, „the above
copyright notice" mitzuliefern — nennt sie niemanden, geht die Erklärung ins
Leere. Eingetragen ist jetzt dieselbe Einheit, die package.json als Autor
führt; test/license.test.mjs hält Platzhalter und Abweichungen künftig auf.
- Die Lizenzfrage ist offener als angenommen.
docs/UEBERGABE.mdging davon
aus, bereits veröffentlichte MIT-Stände ließen sich nicht zurückholen. Es gibt
keine: Das Repository ist privat, es wurde nie etwas herausgegeben — damit
existiert kein Lizenznehmer und keine erteilte Erlaubnis. Korrigiert.
- Die Lizenzfrage blockiert den Launch, nicht nur den Verkauf. Mehrere
Vorlagen in docs/OUTREACH.md bewerben PostOmnia als „quelloffen" bzw.
„open-source" — bei privatem Repository unzutreffend und damit ein UWG-Thema;
durch Veröffentlichen zutreffend gemacht, ist die MIT-Erteilung vollzogen und
unwiderruflich. Hinweis an den betroffenen Stellen ergänzt.
- Marken-Vorprüfung mit Fund: Unter
postomni.comläuft „PostOmni", ein
aktives Produkt derselben Kategorie (Social-Media-Publishing als SaaS). Ein
Buchstabe Unterschied bei identischen Dienstleistungen ist der Lehrbuchfall
der Verwechslungsgefahr. Ob daraus ein Anspruch folgt, kann nur ein Anwalt
klären — Einordnung, Umfeldanalyse (Postiz, Postoria, Postfity, …) und ein
ausformulierter Prüfauftrag samt Nizza-Klassen liegen bei.
- Betreiberwechsel im Ober-Admin. Neues Modul
src/handover.jssamt Karte
unter Einrichtung: Folgenabschätzung, Trennen (nur Plattform-Apps, nur
Dienste und Firmendaten, oder alles) und Inbetriebnahme-Checkliste für den
neuen Betreiber. Kundendaten werden nie angefasst und nach dem Trennen zur
Kontrolle gezählt. Firmendaten gehören ausdrücklich zum Trennen — niemand
darf unter dem Impressum des Vorbesitzers verkaufen.
- Was ein reiner „Alles trennen"-Knopf verschwiegen hätte, meldet die
Oberfläche jetzt vor der Entscheidung: (1) Die OAuth-Tokens der Kunden wurden
von der Developer-App des bisherigen Betreibers ausgestellt und sind mit einer
neuen App wertlos — betroffene Konten werden deshalb **markiert statt
gelöscht** (ein Wechsel ist umkehrbar) und die Kunden bekommen einen Hinweis
in der Benachrichtigungszentrale, statt dass Beiträge still fehlschlagen.
(2) Ein neuer Stripe-Schlüssel ist ein neues Konto ohne die bestehenden Abos.
(3) Werte aus der .env auf dem Server kann der Adminbereich gar nicht
entfernen — die Antwort listet auf, welche Schlüssel dort noch scharf sind.
POSTOMNIA_ENCRYPTION_KEYbleibt bewusst nicht im Adminbereich
wechselbar: Er entschlüsselt die gespeicherten Kanal-Zugänge, ein Wechsel per
Weboberfläche hielte den Totalverlust aller Kundenverbindungen einen Klick
weit entfernt.
- **Wiederherstellungsübung — und ein Fehler in der Anleitung, den sie gefunden
hat.** scripts/restore-test.sh spielt den dokumentierten Weg vollständig
durch: sichern wie backup-db.sh, entschlüsseln wie im Runbook, Inhalt
prüfen, Anwendung auf dem Bestand starten, Statusseite abrufen — mit
Zeitmessung. Der erste Lauf hat sofort geliefert, wofür es die Übung gibt: Die
Wiederherstellungsanleitung kannte nur POSTOMNIA_BACKUP_KEY für die
Sicherungsdatei, nicht aber POSTOMNIA_ENCRYPTION_KEY für die Kanal-Zugänge
darin. Eine Wiederherstellung nach der alten Anleitung wäre scheinbar
geglückt — Datei entschlüsselt, Struktur vollständig, /healthz grün — und
hätte trotzdem bei jedem Kunden tote Zugänge hinterlassen, bemerkt erst beim
ersten fehlgeschlagenen Beitrag mitten im Störfall.
scripts/verify-restore.mjsprüft deshalb vor dem Zurückspielen, ob
Struktur UND Kanal-Zugänge brauchbar sind, und endet mit Exit-Code 1 statt
still durchzulaufen. docs/DEPLOY.md führt den zweiten Schlüssel jetzt in
einer eigenen Tabelle und den Prüfschritt in der Anleitung; die frühere
Angabe „byte-identisch durchgespielt" steht mit der Einordnung dort, dass sie
nur die Datei betraf, nicht die Brauchbarkeit.
docs/WIEDERHERSTELLUNG.mdhält Protokoll, Messwerte und RPO/RTO fest.
Ehrlich getrennt: RPO (max. 24 h) folgt aus dem Cron-Intervall und ist
belastbar, ein RTO ist es nicht — die gemessenen Sekunden gelten für einen
Testbestand. Die Übung gegen eine echte Produktionssicherung bleibt offen,
samt Vorlage zum Ausfüllen. Das Backup-Repo wurde bewusst nicht angefasst: Es
enthält Kundendaten und hat in einer Testumgebung nichts verloren.
- Traktionsnachweis für eine Due Diligence. Neues Modul
src/traction.js:
MRR/ARR, Kundenbestand, Abwanderung und Kohorten – gerechnet aus den
ausgestellten Belegen und den Abo-Zuständen, nicht geschätzt. Export über
GET /api/admin/traction und /api/admin/traction.csv samt mitgelieferten
Definitionen, damit die Gegenseite nachrechnen kann statt zu glauben.
Bewusste Festlegungen: Testphasen zählen nicht in den MRR (ein Trial ist
kein Umsatz, auch wenn er in jeder Pitch-Deck-Vorlage so aussieht),
maßgeblich ist netto (Umsatzsteuer ist durchlaufender Posten), Jahresabos
werden auf den Monat normalisiert statt im Abrechnungsmonat zu verschwinden,
und Kennzahlen ohne Grundlage sind null statt 0 – eine Abwanderungsquote
von „0 %" ohne Kundenbestand wäre eine Falschaussage. Der Snapshot benennt in
caveats selbst, was die Zahlen nicht hergeben.
- Übergabepaket
docs/UEBERGABE.md. Asset-Umfang, Betriebskostenraster,
Inventar aller externen Konten samt Übertragungsweg, Lizenz- und Markenstatus
sowie eine offene Liste der Punkte, die den Preis drücken. Zwei Befunde, die
vorher nirgends standen: Stripe-Abos lassen sich nicht einfach mitverkaufen
(ohne den beantragten Konto-Transfer müssen alle Kunden neu einwilligen), und
POSTOMNIA_ENCRYPTION_KEY ist der kritischste Wert der Übergabe – ohne ihn
sind die gespeicherten Kanal-Zugänge aller Kunden nicht mehr entschlüsselbar.
Das Dokument enthält bewusst nur Namen von Konten und Schlüsseln, nie Werte.
- Externe Verfügbarkeitssonde. Die interne Messung bezeugt nur, dass der
Prozess läuft – bei einem Totalausfall entsteht gar keine Stichprobe, der
Ausfall erschiene als Lücke und damit günstiger, als er war. scripts/uptime-probe.mjs
ruft die öffentliche Adresse von außerhalb auf und misst DNS, TLS, Reverse
Proxy und Anwendung gemeinsam; eingeliefert wird über
POST /api/public/uptime-probe mit POSTOMNIA_UPTIME_PROBE_TOKEN. Externe und
interne Reihe werden getrennt geführt, /status weist die externe aus, sobald
es sie gibt, und benennt die Messquelle – zusammengerechnet würden sie die
belastbare Zahl mit der schwachen verwässern.
- Gepufferte Nachlieferung, eng gefasst. Eine Sonde kann einen Ausfall nicht
an die ausgefallene Anwendung melden; sie puffert lokal und liefert rückdatiert
nach. Damit daraus kein Werkzeug zum Schönrechnen wird: Token im zeitkonstanten
Vergleich, keine Zeitpunkte aus der Zukunft, Rückdatierung höchstens 48 Stunden,
Deduplizierung über eine Stichproben-ID (eine erneut gesendete Charge zählt
nicht doppelt), Rate-Limit. Ohne gesetztes Token ist der Endpunkt aus.
.github/workflows/uptime.ymlals fertiger Messpunkt alle 5 Minuten. Der
Puffer überlebt den kurzlebigen Runner über actions/cache – ohne den ginge
genau die Stichprobe verloren, für die das Puffern gebaut wurde. Dass GitHubs
Scheduler Läufe auslässt, ist dokumentiert: Für ein Verhältnis aus erreichbaren
zu nicht erreichbaren Stichproben senkt das nur die Auflösung.
- Verfügbarkeit wird gemessen, nicht nur zugesagt. § 8 AGB sagt im
Agency-Tarif 99,5 % Monatsverfügbarkeit zu und staffelt eine Gutschrift –
gemessen wurde bisher nichts, und der Vertrag nahm „angekündigte
Wartungsfenster" aus, ohne dass es einen Weg gab, eines anzukündigen. Neues
Modul src/uptime.js: Stichprobe alle fünf Minuten, Tagesbilanzen über 90 Tage,
Monatsverfügbarkeit und Gutschrift nach der Staffel des § 8 AGB. /status und
neu /en/status zeigen die Zahl, einen Tagesbalken je Tag, die offene
Störungs- und Wartungsliste und benennen, was die Sonde messen kann und was
nicht. Tage ohne Messwert bleiben grau statt grün, und eine Messreihe unter
30 Tagen wird ausdrücklich als „im Aufbau" ausgewiesen.
- Wartungsfenster nur mit Ankündigung. Ein Fenster muss vor seinem Beginn
angelegt werden, sonst nimmt die Messung es nicht aus der Zusage heraus – sonst
wäre der bequemste Weg zu guten Zahlen, einen Ausfall nachträglich zum
Wartungsfenster zu erklären. Anlegen und Schließen über
/api/admin/status/incidents, beides bewusst als Hand-off im Adminbereich:
Der Eintrag erscheint sofort öffentlich und verändert, was der Agency-Tarif
schuldet.
- Service-Level ist einstellbar und speist den Vertragstext. Zusage und
Reaktionszeit stehen in POSTOMNIA_SLA_TARGET und
POSTOMNIA_SLA_RESPONSE_DAYS (auch im Adminbereich pflegbar) und füllen die
AGB über {{SLA_TARGET}}/{{SLA_RESPONSE}} aus derselben Quelle wie die
Messung. Eine im Vertragstext einbetonierte Zahl wäre irgendwann von dem
weggelaufen, was die Statusseite belegt.
- Umsatzsteuer im Adminbereich statt nur in der
.env.MERCHANT_TAX_MODE,
MERCHANT_TAX_COUNTRY, MERCHANT_TAX_RATE, MERCHANT_TAX_RATES und
STRIPE_AUTOMATIC_TAX waren die einzigen Größen des Abrechnungspfads, die
Serverzugang und Neustart verlangten – während Firmenname, Anschrift und
Steuernummer im selben Formularblock längst im Adminbereich standen. Der
Wechsel von der Kleinunternehmerregelung zur Regelbesteuerung ist ein
kaufmännischer Vorgang; jetzt ist er auch einer.
- Deutsche Rechtsseiten bekamen englische Formulierungen. Die Platzhalter
wurden mit der aus dem Pfad abgeleiteten Oberflächensprache gefüllt; /agb.html
trägt aber kein Sprachpräfix und fiel damit auf Englisch zurück. Gerendert wird
jetzt in der Sprache des Dokuments.
- Umsatzsteuer im gesamten Abrechnungspfad. Neues Modul
src/tax.js:
Kleinunternehmer (Standard), Regelbesteuerung, Reverse Charge für EU-Geschäfts-
kunden mit USt-IdNr., OSS-Satz für EU-Privatkunden, Drittland nicht steuerbar.
Der Beleg weist Netto, Steuersatz, Steuerbetrag und den Pflichthinweis nach
§ 14 UStG aus. Stripe Tax ist über STRIPE_AUTOMATIC_TAX=1 aktivierbar und dann
maßgeblich; die Preise tragen tax_behavior: inclusive, weil die Listenpreise
brutto sind. Belege erscheinen in der Sprache des Kontoinhabers statt nur auf
Englisch.
- Gutschein-Falle geschlossen. Ohne
STRIPE_COUPON_<CODE>wurde der Rabatt
beim Checkout stillschweigend verworfen, während die Oberfläche den reduzierten
Preis anzeigte – der Kunde sah 15,20 € und zahlte 19 €. Der Stripe-Coupon wird
jetzt wie die Preise find-or-create angelegt; scheitert das, bricht der Checkout
ab. Die Laufzeit steht explizit in src/plans.js (duration).
- Kündigungsbutton nach § 312k BGB.
/kuendigenbzw./en/cancel, ohne
Anmeldung erreichbar, im Footer und in § 5 der AGB verlinkt. Bestätigung in
Textform mit Inhalt und Eingangszeitpunkt, Kündigung zum Ende der bezahlten
Periode (bei Stripe via cancel_at_period_end), Protokoll im Store.
- Auftragsverarbeitungsvertrag nach Art. 28 DSGVO.
/avv.htmlsamt Anlage 1
(TOM, /tom.html) und Anlage 2 (versionierte Subprozessorenliste,
/subprozessoren.html), jeweils auch englisch unter /en/. Annahme pro
Workspace mit Version, Zeitpunkt und handelnder Person
(GET/POST /api/teams/current/dpa).
- Startprüfung der Pflichtangaben (
src/preflight.js): Fehlende
MERCHANT_*-Werte sind ohne Zahlungsdienst nur ein Hinweis, mit aktivem Stripe
eine deutliche Warnung – dann gingen Impressum, AGB und AVV mit Platzhaltern live.
- White-Label für das Kundenfreigabeportal.
/client-review/:tokenist die
einzige Oberfläche, die aus der Agentur heraus zum Endkunden geht, und trug
bisher fest PostOmnia-Branding. Ab Pro (limits.portalBranding === false)
treten Name, Logo, Akzentfarbe und Fußnote der Agentur an die Stelle; das Portal
gibt es jetzt auf Deutsch und Englisch.
- Testphase ohne Kreditkarte. Wer sich für einen bezahlten Plan registriert,
arbeitet sofort los statt auf incomplete blockiert zu werden. Abgelaufene
Testphasen sperren produktive Aktionen nur im Verkaufsbetrieb (Stripe
konfiguriert) – eine selbst betriebene Instanz sperrt sich nicht selbst aus.
Altes Verhalten über POSTOMNIA_REQUIRE_CARD_UPFRONT=1.
- Service-Level für den Agency-Tarif in § 8 AGB (99,5 % im Kalendermonat,
Reaktion binnen eines Werktags, gestaffelte Gutschrift) und
Sicherheits-Onepager unter /sicherheit-onepager.html für Einkauf und
Sicherheitsfragebögen.
npm auditauf 0 (MCP-SDK aktualisiert;hono- undip-address-Lücken
entfallen).
- Agenturanfrage auf der Preisseite. Der einzige Weg ins Produkt war die
Selbstregistrierung; beim Agency-Tarif geht dem Abschluss aber meist ein
Gespräch voraus. Formular mit Honeypot und Rate-Limit, Speicherung in der
bereits vorhandenen Sammlung agencyEnquiries, Eingangsbestätigung an die
Agentur und Meldung an den Betreiber.
- Kundenreport (
src/report.js): druckbares, im Auftritt der Agentur
gebrandetes Dokument mit Kanalentwicklung, Top-Beiträgen und Vergleich zur
gleich langen Vorperiode – plus Link mit Ablaufdatum für Kunden ohne Konto.
Maßgeblich ist der Veröffentlichungszeitpunkt, nicht updatedAt; sonst
tauchten alte Beiträge nach einer Bearbeitung erneut im Report auf.
- Migration aus bestehenden Werkzeugen. Der CSV-Import erkennt jetzt das
Trennzeichen (Semikolon und Tab kommen aus deutschen Excel-Exporten), kennt die
Spaltennamen gängiger Redaktionsplan-Exporte, erlaubt eigene Zuordnung und legt
offen, was er ignoriert hat. Neue Vorschau-Route /api/imports/csv/preview
zeigt das Ergebnis, bevor etwas angelegt wird. Vergangene Termine werden zu
Entwürfen statt rückwirkend geplant.
- Rolle „Kunde". Mitgliedschaften können an genau einen Kunden gebunden
werden (reviewer oder viewer). Ein solcher Zugang sieht nur Beiträge der
Kampagnen dieses Kunden und darf ausschließlich ausdrücklich freigegebene
Operationen aufrufen – Whitelist statt Filter, damit neue Operationen
automatisch gesperrt sind.
- Deploy gehärtet.
sync-to-hetzner.shbricht ab, wenn der Stand hinter
origin/main liegt (genau der Fall vom 07.08.2026), setzt nach erfolgreichem
Deploy ein Versions-Tag und nennt den Rollback-Befehl. Neues
scripts/rollback-hetzner.sh samt Workflow-Eingabe; der Deploy-Workflow liefert
standardmäßig main aus statt des auslösenden Branches und prüft das zusätzlich.
Die Datenbank wird beim Rollback nur auf ausdrückliche Anforderung angefasst.
- Artikelstrecke (
/blog,/de/blog): indexierbare Seiten aus Markdown unter
content/, mit Canonical, hreflang, Article-JSON-LD und Sitemap-Eintrag. Drei
Startartikel zu AVV und KI-Kennzeichnung. Redaktion braucht ab jetzt keine
Codeänderung mehr.
- Die dokumentierte Größe des Capability-Katalogs wird jetzt gegen den echten
Katalog geprüft statt gegen ein Literal – die Zahl veraltete sonst still.
Sicherheit
- Committetes API-Token entfernt. Der AdaptlyPost-MCP-Token in
.mcp.json
wird jetzt über die Umgebungsvariable ${ADAPTLY_MCP_TOKEN} geladen statt im
Klartext im Repo zu liegen (Token muss extern rotiert werden).
- Deploy-Host parametrisiert. Die feste Hetzner-IP in
deploy.yml/docs
ist jetzt über die GitHub-Variable DEPLOY_HOST konfigurierbar; die
DEPLOY_KNOWN_HOSTS-Pinning-Empfehlung wurde deutlicher formuliert (TOFU-Warnung).
- Security-Audit in der CI (
npm audit --audit-level=highder Produktions-Abhängigkeiten)
plus Dependabot für npm und GitHub-Actions.
Entfernt
- Fremdes Nebenprojekt
deploy/autopost/(eigenständiger Telegram-Poster,
ohne Bezug zu PostOmnia) samt Inhalten entfernt.
- Verwaiste Assets ohne Referenzen gelöscht:
public/og.svg,
public/og-agency-light-2026.png und drei alte Screenshots
(postomnia-login/-settings-profile/-team.jpg).
Geändert
- Professionelle Werkzeug-Schicht. Biome (Lint + Format), TypeScript-
checkJs
als opt-in @ts-check-Inseln, .nvmrc, npm-Scripts (lint, format,
typecheck, test:coverage), CI-Quality-Job und Dependabot ergänzt.
engines.node auf >=20 angehoben, Docker-Image auf node:22-alpine.
- Sichere Dependency-Updates. MCP-SDK,
pg,nodemailer(→ 9) und
Version-Floors aktualisiert; bekannte Transitiv-CVEs per npm audit fix
bereinigt. Express 4 und zod 3 bewusst beibehalten (Majors zu risikoreich).
- Doppelte Helfer zusammengeführt. Neues Modul
src/util.js
(escapeHtml, publicBaseUrl, localBaseUrl) ersetzt mehrfach kopierte
Funktionen; Connector-Ergebnisvertrag vereinheitlicht (discord) und
gemeinsamer composeText-Helfer in connectors/base.js.
- Dark Mode. Kompletter Hell/Dunkel-Modus für Landingpage und App:
Umschalter in Landing-Nav und Topbar, gespeicherte Wahl, sonst folgt die
Seite der Systemeinstellung (angewendet vor dem ersten Paint – kein
Aufblitzen). Consent-Banner, Hero, Sidebar und alle Karten folgen dem Theme;
QR-Codes bleiben bewusst auf Weiß (Scanbarkeit).
- Performance: automatische Asset-Versionierung + Langzeit-Caching. Die
?v=-Versionen aller Frontend-Assets werden beim Serverstart aus den
Dateiänderungszeiten berechnet und in alle gerenderten Seiten geschrieben –
ein Deploy invalidiert von selbst. Versionierte Assets sind dafür ein Jahr
unveränderlich gecacht (immutable), HTML bleibt no-cache. Alle Skripte
laden jetzt mit defer; sanftes Scrollen zu Ankern mit korrektem Offset
unter der Sticky-Leiste.
- Adminbereich in Tabs strukturiert. Statt einer langen Seite sechs
Untertabs: Übersicht (Kennzahlen + Aufgaben), Einrichtung & Schlüssel
(Plattform-Apps + Integrationen), Wachstum & Tracking, E-Mail, Nutzer &
Kanäle, Audit-Log. Badges zeigen offene Aufgaben, unkonfigurierte Gruppen
und die Nutzerzahl; der zuletzt offene Tab bleibt gemerkt.
Hinzugefügt
- Indexierbare Link-in-Bio-Landingpage (DE und EN).
/link-in-biound
/de/link-in-bio liefern ausgeloggt eine serverseitig gerenderte Marketing-Seite
mit eigenem Title, eigener Description, Canonical auf sich selbst, hreflang-Paar
und FAQ-Structured-Data; eingeloggt weiterhin die Bio-Verwaltung. Bisher lieferte
/link-in-bio die Startseite samt canonical auf „/" und schloss sich damit
selbst aus dem Google-Index aus – es gab keine Seite, die für den Suchbegriff
kandidieren konnte. Die Unterscheidung läuft über ein reines Anwesenheitsflag
(po_app-Cookie, kein Credential), weil das Auth-Token in localStorage liegt und
der Server den Login-Status sonst nicht kennt; die Route sendet dafür Vary: Cookie.
Beide URLs stehen in der Sitemap.
- Sprachabhängiger Titel und Beschreibung der Startseite.
/de,/en,/es,
/fr und /pt lieferten bisher identische Meta-Angaben, weil die Übersetzung erst
im Browser stattfindet – Google sah fünf gleiche Seiten. Titel und Description
kommen jetzt serverseitig je Sprache.
- Eigene Hintergrundfarbe für Link-in-Bio-Seiten. Neben der Akzentfarbe lässt
sich jetzt die Fläche der ganzen Seite einfärben (bisher fest weiss bzw.
dunkelblau aus dem Theme). Schrift-, Karten- und Linienfarben werden aus der
gewählten Farbe abgeleitet: Die Helligkeit der Fläche entscheidet über helle
oder dunkle Schrift, nicht mehr allein die Theme-Auswahl – eine dunkle
Wunschfarbe bleibt also auch im Light-Theme lesbar. Ohne eigene Farbe bleibt
alles wie bisher.
- Werbe-Pixel mit Marketing-Einwilligung (Meta, Google, TikTok). Neue
Consent-Kategorie „Marketing" mit granularen Schaltern (Analyse und Marketing
getrennt wählbar); Meta Pixel, Google GA4/Ads und TikTok Pixel laden
ausschließlich nach erteilter Zustimmung. Pixel-IDs pflegt der Ober-Admin
(Settings → Werbe-Pixel); ein öffentlicher Endpoint liefert nur gesetzte IDs.
Die Content-Security-Policy wird nur dann für die Werbe-Domains geöffnet, wenn
tatsächlich ein Pixel hinterlegt ist – sonst bleibt sie eng. Datenschutz-
erklärung um die Drittanbieter ergänzt. Consent-Version auf 2 erhöht
(bestehende Nutzer werden erneut gefragt).
- Empfehlungsprogramm. Jeder Nutzer hat einen persönlichen Empfehlungslink
(postomnia.de/r/<code>). Wer sich darüber registriert, bekommt
7 zusätzliche Testtage; sobald der Geworbene seine E-Mail bestätigt
(Missbrauchsschutz), erhält der Werber 30 Bonus-KI-Credits – ein eigener
Credit-Topf, der nicht monatlich verfällt und erst nach dem Plan-Kontingent
verbraucht wird. Neue App-Ansicht „🎁 Empfehlen" mit Link, Kopier- und
Share-Buttons (WhatsApp, Telegram, X, LinkedIn, E-Mail) und Statistik
(Klicks, Anmeldungen, bestätigt, verdiente Credits) samt Empfehlungsliste
mit maskierten E-Mail-Adressen. Benachrichtigungen bei Anmeldung und
bestätigter Belohnung.
- Magic Time. ✨-Button im Composer schlägt die besten Posting-Termine vor –
gelernt aus dem Engagement der eigenen veröffentlichten Posts (Wochentag +
Stunde, Europe/Berlin); mit wenig Daten bewährte Standard-Slots.
- Evergreen-Recycling. Veröffentlichte Posts per ♻️ sofort neu einplanen
(KI-Umformulierung, falls konfiguriert) oder per 🌲 auf ein 28-Tage-Intervall
stellen – der Scheduler plant sie dann automatisch zum nächsten
Magic-Time-Slot neu.
- Link-in-Bio: E-Mail-Capture. Anmeldeformular auf der öffentlichen Seite
mit Double-Opt-in per Mail, Abonnenten-Übersicht und CSV-Export im Editor.
- Link-in-Bio: A/B-Test für Links. Zweite Beschriftung je Link, 50/50
ausgespielt; Impressionen und Klicks je Variante, CTR-Anzeige und
„Gewinner übernehmen" im Editor.
- Link-in-Bio: QR-Poster. Druckfertige A4-Seite (/bio/<handle>/poster)
mit großem QR-Code, Profilbild und Titel – PDF-Export über den Druckdialog.
- Link-in-Bio: Link-Health-Check. Täglicher Check aller Links
veröffentlichter Seiten (SSRF-sicher) plus „Links prüfen"-Button; kaputte
Links bekommen ein Warn-Badge und eine Benachrichtigung.
- Landingpage: Instagram-Verlinkung & Bio-Beispielseite. Instagram-Icon in
Navigation und Footer – eingebauter Standard (offizielles PostOmnia-Profil),
überschreibbar per Ober-Admin (SOCIAL_INSTAGRAM_URL). Neue fest kuratierte
Beispiel-Bio-Seite unter /bio/beispiel (Handle reserviert) mit Featured-Link,
Abschnitts-Überschrift, Teaser-Drop und Instagram-Profil; Profil- und
Headerbild kommen live von der Link-in-Bio-Seite des Ober-Admin-Kontos. Die
Telefon-Demo im Link-in-Bio-Abschnitt zeigt und verlinkt diese Seite, der
„Scan mich"-Chip trägt jetzt ihren echten, scannbaren QR-Code.
- Adminbereich: Plattform-Apps & API-Schlüssel. Client-ID/Secret aller
OAuth-Plattformen (X, LinkedIn, Meta/Facebook+Instagram, Threads, Pinterest,
Reddit, TikTok, Google/YouTube) sowie Integrations-Schlüssel (Anthropic,
Gemini, SMTP, Stripe, Turnstile, Firmendaten, Social-Links) direkt im
Adminbereich pflegbar: verschlüsselt gespeichert (AES-256-GCM), sofort aktiv
ohne Neustart, Vorrang vor der .env mit sauberem Fallback beim Löschen.
Je Plattform-App: Status-Badge, Link ins Developer-Portal, Einrichtungs-Hinweis
und kopierbare Redirect-URLs. Live-Verbindungstests für SMTP, Stripe,
Anthropic und Gemini. Secrets verlassen den Server nur maskiert; Änderungen
landen (ohne Werte) im Audit-Log.
[0.1.0] – 2026-07-04
Erste Veröffentlichung
Publishing & Kanäle
- 14 Kanäle (Telegram, WhatsApp, Discord, Mastodon, Bluesky, X, Instagram,
Facebook, LinkedIn, TikTok, YouTube, Pinterest, Threads, Reddit) mit OAuth 2.0
bzw. Credential-Auth.
- Composer, Kalender, Scheduler, Wiederholung fehlgeschlagener Kanäle.
- Media-Hosting mit Streaming- und Chunked-Upload.
Steuerung
- MCP-Server für Claude und REST/OpenAPI für ChatGPT auf gemeinsamer Kernlogik.
Teams & Konto
- Multi-User-Teams mit Rollen, E-Mail-Einladungen, Passwort-Reset.
- Strikte Team-Datenisolation.
Abrechnung
- Abo-Modelle mit 7-Tage-Trial, Jahresrabatt und Gutscheinen.
- Belege/Rechnungen (druckbar), Stripe-Integration (optional).
Analytics
- Kennzahlen pro Post/Kanal, aggregiertes Team-Reporting.
Betrieb & Sicherheit
- Brute-Force-Schutz, Rate-Limiting, strukturierte Logs, Prometheus-
/metrics. - DSGVO: Datenexport, Team-/Konto-Löschung.
- Docker-Deployment mit automatischem TLS (Caddy), optional Postgres.
- Tests (Node-Test-Runner), CI/CD (GitHub Actions).
Marketing/Launch
- SEO (Meta, Open Graph, robots.txt, sitemap.xml, schema.org), Social-Card,
Landing-/Pricing-Page, Rechtstexte (Impressum/Datenschutz/AGB inkl. Widerruf),
Cookie-Consent-Banner.
[0.1.0]: https://github.com/PhiFa96/postomnia/releases/tag/v0.1.0