A self-hosted B2B ordering portal for wholesale suppliers to manage retail buyer orders. Built with SvelteKit + Cloudflare D1 + DrizzleORM. Part of the Alcogy OSS series alongside Cork (CRM) and Galway (WMS).
Supplier (admin)
Buyer (retailer)
System
wrangler deploy| Layer | Technology |
|---|---|
| Frontend / SSR | SvelteKit 2 (Svelte 5 runes) |
| Adapter | @sveltejs/adapter-cloudflare |
| ORM | DrizzleORM |
| Database | Cloudflare D1 (SQLite) |
| Storage | Cloudflare R2 |
| Auth | PBKDF2 via WebCrypto API |
| Adapter pattern (Resend / SES / SMTP / Cloudflare Email) | |
| Package manager | Bun |
git clone https://github.com/your-org/limerick.git
cd limerick
bun install
# D1 database
wrangler d1 create limerick-db
# → copy the database_id into wrangler.jsonc
# R2 bucket (for product images)
wrangler r2 bucket create limerick-storage
Update the database_id in wrangler.jsonc:
"d1_databases": [
{
"binding": "DB",
"database_name": "limerick-db",
"database_id": "<your-database-id>", // ← paste here
"migrations_dir": "./drizzle"
}
]
Set the email vars (see Email configuration):
"vars": {
"EMAIL_PROVIDER": "resend", // "resend" | "ses" | "smtp"
"EMAIL_FROM": "[email protected]",
"ALERT_EMAIL_TO": "[email protected]",
"SUPPLIER_NAME": "Acme Trading Co."
}
# Local development
bun run db:migrate:local
# Production (Cloudflare D1)
bun run db:migrate:remote
Use the first-run wizard at /setup after starting the app, or insert manually:
# Seed an initial supplier (password set via invitation flow)
wrangler d1 execute limerick-db --local --command \
"INSERT INTO users (id, email, name, role, is_active, password)
VALUES (lower(hex(randomblob(16))), '[email protected]', 'Admin', 'supplier', 1, null)"
Then navigate to /setup to set a password.
bun run dev
# → http://localhost:5173
bun run build
wrangler deploy
Limerick supports four email providers. Set EMAIL_PROVIDER in wrangler.jsonc and the corresponding secret via wrangler secret put.
"vars": { "EMAIL_PROVIDER": "resend", "EMAIL_FROM": "[email protected]" }
wrangler secret put RESEND_API_KEY
"vars": { "EMAIL_PROVIDER": "ses", "EMAIL_FROM": "[email protected]" }
wrangler secret put AWS_ACCESS_KEY_ID
wrangler secret put AWS_SECRET_ACCESS_KEY
wrangler secret put AWS_REGION # e.g. ap-northeast-1
"vars": { "EMAIL_PROVIDER": "smtp", "EMAIL_FROM": "[email protected]" }
wrangler secret put SMTP_API_URL # HTTP relay endpoint
wrangler secret put SMTP_API_KEY
Enable Cloudflare Email Routing on your domain. The send_email binding is already declared in wrangler.jsonc and is used automatically for admin alert emails when available.
Buyer invitation and order message templates are editable in the supplier UI at Settings → Email Templates. Variables use {{variable_name}} syntax.
| Template | Variables |
|---|---|
| Buyer Invitation | buyer_name, company_name, supplier_name, setup_url, expires_at |
| Order Message | buyer_name, order_number |
Declared in wrangler.jsonc under vars (non-secret) or set via wrangler secret put (secret).
| Variable | Type | Description |
|---|---|---|
EMAIL_PROVIDER |
var | resend | ses | smtp |
EMAIL_FROM |
var | Sender address (must be verified) |
ALERT_EMAIL_TO |
var | Fallback admin alert recipient |
SUPPLIER_NAME |
var | Displayed in invitation emails |
RESEND_API_KEY |
secret | Resend API key |
AWS_ACCESS_KEY_ID |
secret | AWS access key (SES) |
AWS_SECRET_ACCESS_KEY |
secret | AWS secret key (SES) |
AWS_REGION |
secret | AWS region, e.g. ap-northeast-1 (SES) |
SMTP_API_URL |
secret | HTTP relay endpoint (SMTP mode) |
SMTP_API_KEY |
secret | HTTP relay API key (SMTP mode) |
Set a secret:
wrangler secret put RESEND_API_KEY
/setup — first-run wizard (supplier account creation)
/login — public sign-in
/auth/setup-password — buyer invitation activation (token-based)
/logout — sign out
/supplier/dashboard — requires role=supplier
/supplier/products
/supplier/categories
/supplier/price-groups
/supplier/buyers
/supplier/orders
/supplier/orders/[id] — order detail with status management
/supplier/invoices
/supplier/invoices/[id]
/supplier/profile
/supplier/audit-log
/supplier/email-templates
/supplier/settings
/buyer/catalog — requires role=buyer
/buyer/cart
/buyer/orders
/buyer/orders/[id]
/buyer/invoices
/buyer/invoices/[id]
/buyer/profile
bun run db:generate # generate new migration from schema changes
bun run db:migrate:local # apply to local D1 (wrangler dev)
bun run db:migrate:remote # apply to production D1
bun run db:studio # open Drizzle Studio
bun run dev # start dev server
bun run build # production build
bun run check # TypeScript + Svelte checks
bun run lint # ESLint + Prettier
bun run format # auto-format
bun run test:unit # Vitest unit tests
bun run test:e2e # Playwright e2e tests
MIT