OfferNow is a small, password-protected offer viewer. It turns a private folder of Markdown documents into a client-facing offer page with a document table of contents and an "Angebot ansehen" call-to-action for the signing flow.
It is designed for sending polished, read-only offer material without publishing
the offer content itself. The public homepage does not list offers; each offer is
reachable only through its /o/<slug> URL and password gate.
Use the $offernow-create-offer skill to create a new offer with Cursor,
Codex, Claude, or another coding agent.
- Astro 5 with
output: "server"and the Cloudflare adapter. - Tailwind CSS 4 for styling.
- Alchemy for Cloudflare Worker provisioning.
- Astro content collections for Markdown offer documents.
- pnpm for package management.
Each offer has two parts:
- Source configuration in
src/offers.config.ts, containing the offerslug, displaytitle,client, anddocusealUrl. - Private Markdown content under
src/data/offers/<slug>/.
Real offer Markdown lives under src/data/offers/ locally. That directory is
gitignored by default so private client documents are not published with the
open-source repository.
Example document layout:
src/data/offers/clientabc/
dokument-1.md
dokument-2.md
leistungsumfang.md
Each document is a Markdown file with frontmatter:
---
title: "Leistungsumfang"
order: 1
description: "Kurzbeschreibung des Dokuments."
---
## Abschnitt
Generischer Beispieltext fuer das Angebotsdokument.OfferNow uses one password per offer. Passwords are read on the server from
environment variables and are never stored in src/offers.config.ts.
OFFER_SESSION_SECRETsigns the httpOnly offer session cookie.OFFER_CLIENTABC_PWis the password for the example/o/clientabcoffer.- General convention:
OFFER_<SLUG_UPPER>_PW; non-alphanumeric slug characters become underscores, e.g.client-abcusesOFFER_CLIENT_ABC_PW.
After a successful login, the server sets a signed httpOnly cookie for that offer. Document routes remain blocked unless the cookie is valid.
- Install dependencies with
pnpm install. - Copy
.env.exampleto.env. - Set
OFFER_SESSION_SECRETto a long random value. - Add one
OFFER_<SLUG_UPPER>_PWvalue for each local offer. - Run checks or builds with
pnpm exec astro checkandpnpm run build.
The dev server is intentionally not required for repository verification.
Use the project skill at .cursor/skills/offernow-create-offer/SKILL.md when
creating a new offer. It documents the repeatable process:
- Choose a deploy-safe slug such as
clientabcorclient-abc. - Add private Markdown documents under
src/data/offers/clientabc/. - Register the offer in
src/offers.config.ts. - Add
OFFER_CLIENTABC_PWor the matching underscore-normalized key to.env. - Verify with
pnpm exec astro checkandpnpm run build.
Example registry entry:
{
slug: "clientabc",
title: "Angebot - Client ABC",
client: "Client ABC",
docusealUrl: "https://example.com/sign/clientabc",
}/is a minimal public landing page with no offer list./o/[offer]renders the password gate or the authenticated offer overview./o/[offer]/[doc]renders one authenticated offer document./api/offer-loginvalidates offer passwords and sets the session cookie.
alchemy.run.ts provisions the Cloudflare Worker. Deploy manually from a
trusted local environment after alchemy login and .env are configured:
pnpm deploy:alchemyDeploy-time secrets are read from local environment variables:
ALCHEMY_PASSWORDCLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_ID, when not using OAuthOFFER_SESSION_SECRET- one
OFFER_<SLUG_UPPER>_PWvalue per deployed offer
There is intentionally no GitHub deploy workflow.
- Do not commit
.envor real passwords. - Do not commit real client offer Markdown.
- Keep
src/data/offers/ignored unless you intentionally add sanitized sample content. - Keep
src/offers.config.tsempty or populated only with clearly fake examples in the public repository. - Share offer passwords out-of-band, never in URLs or public issue trackers.
