I've been building Melororium — a lifetime project management workspace for small teams — and the two most interesting product decisions I made weren't about features. They were about what not to include by default.
The architecture problem with "all-in-one" tools
Every project management tool eventually tries to be everything. ClickUp added Docs. Notion added tasks. Asana added timelines. The result is tools that are bloated for most users and still missing the one thing you actually need.
When I started building Melororium, I kept asking: what does every team actually use every single day?
The answer was shorter than I expected:
Task management (Kanban + List + Calendar)
Time tracking inside tasks
Client CRM
Financial reporting + invoicing
Team management + work reports
Dashboards
That became the 12 core modules — shipped in every plan, no exceptions.
Everything else became opt-in. Not locked behind a higher tier. Just... not forced on you if you don't need it.
The AI decision
This is the one I want to talk about most.
When I built the first version of AI features, the obvious move was to pick one provider, wrap it, charge for it in a premium tier.
I didn't do that. Here's why.
First: teams already have AI preferences. Some are locked into OpenAI for compliance reasons. Some prefer Claude's reasoning for analytical tasks. Some use Gemini because they're already in the Google ecosystem. Making them switch providers — or pay twice for the same model through my platform — felt wrong.
Second: the margin math is bad for both sides. If I host the AI calls, I'm paying inference costs and either subsidizing them or marking them up. Neither is great. The user pays more than they should. I'm running a business I didn't sign up for.
Third: AI usage patterns are unpredictable. Some teams will use the AI analytics module ten times a day. Others will open it once a month. Flat-rate AI pricing is always wrong for someone.
So I built BYOK — bring your own API key.
export async function getAIClient(workspaceId: string) {
const settings = await getWorkspaceSettings(workspaceId);
switch (settings.aiProvider) {
case 'openai': return new OpenAI({ apiKey: settings.apiKey });
case 'anthropic': return new Anthropic({ apiKey: settings.apiKey });
default: return new GoogleGenerativeAI(settings.apiKey);
}
}
One function. Every AI action in the codebase calls getAIClient() first. Provider switching is a single workspace setting. Keys are encrypted at rest, never logged.
The user connects the provider they already use. They control the costs directly. I don't touch their inference budget.
The modular pricing that followed
Once I made AI opt-in, the same logic applied to other modules. Not every team needs the same stack.
The architecture became:
Core (12 modules) — included in every plan
Add-ons — picked at purchase, locked per license
Three tiers:
Starter — $149 once → 4 users, all 12 core modules
Agency — $299 once → 10 users, 12 core + pick 5 add-ons
Studio — $499 once → 25 users, 12 core + all announced add-ons
No recurring fees. No seat tax as you grow within your tier.
What this forced technically
Making add-ons genuinely modular — not just hidden behind a feature flag — meant the core and add-on layers had to be cleanly separated from day one.
Every module has its own data schema, its own API routes, its own UI surface. The workspace config object tracks which add-ons are unlocked. Every protected route checks the license before rendering.
It's more work upfront. But it means the codebase scales to 40 modules without becoming a tangled mess of conditional rendering and permission checks scattered everywhere.
Stack, if you're curious
Next.js 14 App Router, TypeScript, Tailwind. Vercel for deployment. The AI layer sits behind a clean adapter so swapping providers is a 5-line change per new integration.
Pre-launch now, founding price closes July 30. If you're building something and want to see how the modular add-on system works in practice, drop a question below — happy to go deeper on any part of the architecture.
Top comments (0)