Groselia
Social media that runs itself
My role
- Co-founder
- Engineering
- Product and brand design
Description
Groselia runs social media for small businesses: strategy, design, publishing, comment replies and reporting, on every channel, from one platform.
Team
Danilo ParmegianiCo-founder
Pedro CritchiCo-founder
Context
I started Groselia with Pedro as a team of two. He runs marketing, sales and operations; I designed the brand and the product and built the platform it runs on, from the database to the last animation. It runs our customers' content and social media today.
Tools
- Codex
- Claude Code
- Figma
- Adobe tools
Stack
React
Next.js
TypeScript
Tailwind
Supabase
Anthropic LLMs
Trigger.dev
Stripe
Vercel
Timeline
- 2025–now
By the numbers
- 9,700+
- commits
- 520+
- pull requests
- 426
- database migrations
- 47
- edge functions
- 8
- social networks
- 17
- languages
Challenge
Building stopped being the bottleneck. Being seen became it.
AI collapsed the cost of making software. A founder with no engineering background can ship a product in a weekend now, and so can everybody else. What didn't get cheaper was getting anyone to notice it. Distribution is a continuous operation, not a task: posting every day, on eight networks that each want a different format, answering every comment, and reading numbers that each app reports differently.
Small businesses pay for that in one of three ways. They hire a team they can't afford, they stitch together a dozen tools that each solve one slice, or they post nothing and pay with growth. Groselia exists to be the fourth option.
How it got here
Groselia changed shape four times before it found what it is. Every turn kept what worked and dropped the rest.
Now
Service as a software
We sell the work, not the tool. Our team runs each customer's social media on the platform, which makes one person able to run what used to take an agency.
Sep 2026
A social media manager
Customers didn't want AI to make their content. They wanted their own photos posted well and their comments answered. We cut generation and went deep on running the channels.
Aug 2026
Lovable for marketing
One conversation instead of a form: describe the campaign and Groselia plans, writes and publishes it. A chat agent with tools, not a roster of bots.
Apr 2026
Motion video
Groselia started as Apple-style launch videos made from After Effects templates. I validated the whole pipeline by hand, and then saw it couldn't scale to the quality bar.
2025
Argibee
An AI brand-design platform. It didn't find its market, but its brand-kit engine became the spine of everything after it.
Brand
I wanted Groselia to look like a friendly consumer app, not marketing software: something a café owner would smile at, sitting on a phone next to the apps they love. I designed the brand myself in Figma, and every decision went on one board, measured, so it could be compared and argued with.
The symbol
The symbol came first and stayed. Three soft petals turning around a centre, it carried over from the very first version of the company, and the new logo was built around it rather than over it: its geometry was copied and scaled, never redrawn.
The wordmark
The wordmark is TT Commons Pro Bold, lowercase, outlined into vectors so it renders the same everywhere. At its natural spacing it felt loose next to the symbol, so I tightened it to −4% tracking by moving each letter left by an extra 6.4 units at 160px. It sounds tiny; it took the word from 564 to 520 units wide and made it read as one shape.
A grid of H
Everything is measured in H, the wordmark's visible height, on a grid of H/10. That way the logo scales without anyone having to eyeball it again.
For the stacked logo I drew six options, three symbol sizes (1.50H, 1.75H, 2.00H) against two gaps (0.15H, 0.25H), and we picked B: a 1.75H symbol, 0.15H above the word. For the horizontal one I compared gaps from 0.20H to 0.35H and settled on a 1.10H symbol with a 0.35H gap.
The stacked logo on its construction grid. H is the wordmark's height; the grid is H/10.
Horizontal
Symbol 1.10H · gap 0.35H
Stacked
Symbol 1.75H · gap 0.15H
Symbol
Original ratio, 0.93 : 1
Colour
The palette is fifteen colours, loud on purpose. But the logo only ever comes in four treatments: blue symbol with black text, blue with white, all black and all white. The other colours are grounds, never the symbol.
To prove that, I put the symbol and both lockups on twelve backgrounds, 24 studies each, and kept the ones where its silhouette survives. On blue grounds the symbol goes monochrome. Click a colour to copy it.
Typography
TT Commons Pro does every job: six weights from Light to ExtraBold, an eleven-step scale from 12 to 96px, and true italics. I migrated all 688 text layers in the file to it, so the brand book, the app and the website now speak in one voice.
Primary font
TT Commons
Pro
App icon
The app icon is the one place the brand goes 3D: the symbol in porcelain, layered, lit from above and a little glossy. In a later refresh I shrank the symbol inside it by 10% so it breathes in its tile. It also gets its own lockup with the wordmark, the tile at 1.5H.
A world of 3D objects
Around the logo lives a cast of 3D objects: a heart with googly eyes, chat bubbles, a hashtag, sparkles. They're the brand's voice on the website and in our posts, and they're never still: they float, and on the site they react to the cursor. These do too: shove them, or pull one back and let it go.








Neo doodles
For everything flat there's Neo, our icon set: thick black outlines, fills from the palette and one white shine. On the website each one starts as a plain line icon in a white chip, and turns into its doodle when you hover it, its parts arriving one at a time: the disc pops, the bars drop, the shine draws. These are the website's own doodles and animation, copied as-is. Hover one.
Product
Everything in Groselia is done by talking. You say what should go out and when, and it schedules it, publishes it, answers the comments and tells you what worked. The work arrives as one plan the owner approves, instead of a dozen screens to operate.
A conversation, not a dashboard
Groselia started as a form: pick a goal, fill in the fields, get a campaign. Nobody wants to fill in forms about their own business, so now every brand has one conversation instead.
You open your brand and talk. Ask what you should post this week, drop in photos from your phone, or say “answer yesterday's comments”. The agent looks before it answers: your calendar, what has performed, the brand's memory.
When it needs something from you, it asks one short round of questions you answer with a tap. Then it proposes one plan: the posts with their dates and times, the replies, any changes. You edit what you want, approve it, and watch the calendar fill in.
The card here is the real chat, rebuilt in the browser from the app's own tokens. Go ahead and use it.
Designed twice: for owners, then for us
The first audience was small business owners who run their own social media. They don't think in campaigns, funnels or posting cadences; they think “what do I post this week?”. So the hard part was hiding the machinery: onboarding in four questions, a brand kit filled in from a link, a calendar you can drag, every screen in seventeen languages and usable on a phone.
Then we learned customers didn't want to operate any tool, however simple. They wanted the work done. So we became the main users. One person on our team runs many brands, and the same app had to grow multi-brand workspaces, cycles that open with a meeting and close with a report, an approval step on every post and one inbox for every network. Customers still log in, but now the app is where they watch the work, not where they do it.
Built to be read by agents
More and more, the first reader of a brand or a web page is an AI. So everything in Groselia is written for agents as much as for people. A brand's memory is a set of plain Markdown documents, preferences, do's and don'ts, brand facts, that the agent reads before every reply and edits itself with four tools, the way AI assistants keep their own memory. People open and edit the very same documents in the brand kit.
Insights run through one shared core that both the dashboard and the agent read, so when you ask “what's working?”, the answer comes from the numbers you see. And the website does the same for the assistants people ask about us: groselia.com serves an llms.txt, a longer llms-full.txt and a comparison file, all generated from one source of facts in the code, so what an AI says about our prices or security can't drift from the site.
Key features
A brand kit from a link
Paste a website and connect your accounts. Groselia scrapes the site, reads your past posts and fills in logo, colours, fonts, voice and audience on its own.
One conversation
An AI agent with 41 tools captions your media, picks times from your own engagement data, reads metrics and drafts replies, then bundles it all into a plan.
A calendar for eight networks
Instagram, LinkedIn, TikTok, YouTube, X, Facebook, Threads and Reddit, each in its native format, with past posts imported so the calendar is never empty.
Every comment answered
Comments and DMs from every network in one inbox, with replies drafted in the brand's voice and anything sensitive held for a person.
Insights in plain language
Reach, views and engagement across every account, honest about what each platform does and doesn't report, and an answer to “what's working?”.
Cycles and approvals
Work runs in cycles. Each opens with a meeting, closes with a report the AI writes from the numbers, and every post can wait for the customer's approval.
Website
The website is where the brand gets loud. I designed and built it in Next.js. It's in seventeen languages, and it hands each visitor's language over to the app when they sign up.
A hero you can play with
The logo turns in while the page loads, then the hero: 3D props that float, lean with the mouse and get shoved around by the cursor, and a scroll scene that ends in “Made for you”.

The real app, rebuilt
“Inside your Groselia” shows the platform as it is, rebuilt in code from the app's own screens with a demo brand.

A blog
Notes on running social media. Posts are plain MDX files, one per language, with no CMS, and each gets its own feed and structured data so search engines and AI assistants read it right.

A form that qualifies leads
Every “Book a call” opens a short form before the calendar: who you are, where you post, what you need and your budget. Each step is saved to our database as you go, so we see even half-finished answers, and a finished form pings our team on Slack.

Responsive, always
Every page is designed for phones too, not just shrunk to fit. Where there's no hover, the sections wake as they reach the middle of the screen.



Engineering
Architecture
I built the platform myself, with AI as my pair. The app is React and Vite, the landing is Next.js, and both sit on one Supabase project: Postgres with row-level security, auth, realtime and storage, and 47 edge functions. Anything slow or scheduled runs on Trigger.dev, the social networks come through one publishing API, and Stripe handles plans, cards and Brazilian boleto.
There's also an MCP server and a Codex plugin, so Groselia can be used from inside an AI agent: the same brands, posts and drafts, as tools.
Where it's used
The core
Behind it
The app, the website and the MCP server are hosted on Vercel.
Hover or tap a box to see what it does and what it talks to.
How a post travels
The best way to see how the parts fit is to follow one post, from a sentence in the chat to its numbers on Insights. Click through the steps, or let them play.
Web app
The owner asks
“Post the harvest photos on Friday, Instagram and LinkedIn.” The message goes to the agent's edge function, which streams its answer back.
The agent
The agent runs on Anthropic's LLMs, as one streaming edge function with a tool loop. The model gets 41 tools of its own plus web search and web fetch, up to eight hops per turn. The hard part was never the model. It was making sure the agent always knows what's true right now.
So every turn, before the model sees the message, the function rebuilds the context from the database: the date and time in the owner's timezone, the brand's memory documents, the live calendar, the open plans, uploaded documents and a rolling summary of older messages. The stable part of the prompt is cached for an hour; this part is never cached, so it can't go stale.
Two rules shaped the rest. Anything public or irreversible goes into a plan the owner approves, never straight out. And a post only exists if a tool created it this turn, which ended the days when the agent described work it hadn't done. When Anthropic's mid-size model got good enough, I moved the chat to it from their largest one, to cut the cost per conversation.
Context, rebuilt every turn
Tool calls
Plan, waiting for approval
Harvest week: 3 posts, 4 replies
// Volatile block: time + memory + live calendar + active plans + rolling summary. // Rebuilt each turn (never cached) so the agent always sees current state. const volatileSystem = () => { const parts = [ nowBlock, renderMemories(memories), renderCalendar(calendar, todayISO), renderPlans(plans), ] if (documents.length) parts.push(renderDocuments(documents)) if (rollingSummary) parts.push(`STORY SO FAR (summary of earlier messages in THIS conversation):\n${rollingSummary}`) // … then a line for each of the brand's other conversations return parts.join('\n\n') }
Data model
Around 55 tables, in a few clear groups. A workspace has members and brands; everything else hangs off the brand, so row-level security can answer one question everywhere: is this person a member of this brand?
Who
- Workspaces
- Members
- Brand roles
- Profiles
The brand
- Brands
- Colours, fonts, logos
- Products and people
- Memory documents
- Brand documents
Talking
- Conversations
- Messages
- Plans
- Plan items
The calendar
- Posts
- One per network
- Post metrics
- Post comments
Channels
- Connected accounts
- Daily account metrics
- Channel settings
Inbox and cycles
- Inbox threads
- Inbox messages
- Reply drafts
- Cycles
- Cycle reports
Everything below “Who” carries a brand id; row-level security checks it on every read and write.
Reliability and security
Groselia publishes to real accounts, so the costly failures are a post going out twice, or a post that silently never goes out. Most of the engineering is there.
1Approving twice runs once
The plan runner claims the plan, then each item, with a conditional update, and every idempotency key comes from the item's own id. A double click or a retry replays instead of posting again.
2Webhooks are signed and replayed
Every delivery from the publishing API must carry a valid HMAC signature. Handlers are idempotent because deliveries can arrive more than once, and a job replays any missed event every ten minutes.
3Refuse before queueing
One shared rule file says what each network accepts (Instagram needs media, YouTube needs video, no mixing on Facebook). The scheduler checks it before anything is queued, and a refused post comes back as Failed, with the reason, instead of sitting on the calendar as Scheduled.
4Access checked on every call
Row-level security in Postgres, a role check in every edge function, and MCP access through OAuth with PKCE, scoped per brand and per action, revocable from one page.
For a while my local app and the live one shared a single production database, and a migration that ran ahead of a deploy once emptied the live Brand Kits page. Since then backend work happens on a Supabase branch first and merges into production with the code that needs it.
Quality and workflow
About 1,000 test cases across 140 test files cover the app, the MCP server and the edge functions. Some checks are there because something broke once: a parity test fails the build if any of the 17 languages misses a key, and the database suites for the MCP run on synthetic data and roll back at the end.
The agent has evals of its own. One asks the live agent a fixed set of questions about a brand's performance, each in a fresh conversation, and checks that it read Insights with the right period and that its answer carries the same numbers the Insights page shows.
One person shipped 520+ pull requests by treating AI agents like a team. Every change gets its own branch in its own git worktree, so Claude Code and Codex can work on several things at once without stepping on each other. Each one runs its tests and type checks, opens a small pull request, and nothing merges before I've read it.
Technical challenges
Each one looked like a bug in the interface and turned out to be something deeper: the shape of a stream, a timestamp, what a network really reports, a clock.
Making generation feel live · Realtime
Generating a month of posts took a spinner and then twelve posts popping in at once. It felt slow, but the model wasn't the problem. The granularity was.
I switched the model's output to JSON Lines and streamed it, inserting each post the moment its line closed. The calendar subscribes to Postgres changes over Supabase Realtime and paints placeholders first, so posts type themselves in one by one.
The same wait now feels like watching someone work.
Two timestamps that never matched · Data
Supabase warned the database was about to run out of disk IO. Reads were fine. Something was rewriting the same rows: 344k inserts to store 83k messages.
The inbox sync compared the timestamp it saved with the one it read back. Postgres returned it as +00:00 and we wrote it as Z, so every conversation looked changed and every sweep re-downloaded it. Comparing instants instead of strings fixed it.
export function sameInstant(a: string | null | undefined, b: string | null | undefined): boolean {
if (a == null && b == null) return true;
if (a == null || b == null) return false;
if (a === b) return true;
const ta = Date.parse(a);
const tb = Date.parse(b);
return Number.isFinite(ta) && Number.isFinite(tb) && ta === tb;
}Inserts to store 83k messages
344k→one per new message
Conversations re-read each sweep
all→the changed ones
Write churn stopped, and inbox histories that never finished synced for the first time.
A zero isn't always a zero · Analytics
Our analytics provider returns one shape for every network and fills whatever a network doesn't report with 0. Instagram “had” zero clicks on every post. Charts lied politely.
A per-platform metric registry that records what each network really reports, nulls the rest at ingest, and lets every chart show, degrade or hide a metric honestly.
Insights across eight networks that never show a number nobody measured.
Logged out by a fast clock · Auth
My co-founder was logged out the instant he signed in: fifteen token refreshes a second, then a rate limit. Nobody else could reproduce it.
His computer's clock was over an hour fast, so every fresh token looked expired. I wrapped the session storage to re-anchor expiry to the local clock when a session is written.
Token refreshes per second
15→0
Sign-in works on any device clock, and the console says when one is off.
Takeaways
Taste is the moat
When AI makes building cheap, everyone can ship, and most of it looks the same. What's still scarce is judgment: knowing what to leave out, how a screen should feel, when an animation is one too many. For a team of two that can't outspend anyone, taste is what makes Groselia look and work like something much bigger, and it's the one part I'd never hand to a model.
Sell the outcome, not the tool
Nobody wanted to operate marketing software, however good. They wanted to be seen. Selling the work instead of the app made the product matter more, not less: it became the engine we run every day, so every rough edge got fixed.
Context beats cleverness
The agent got better when it knew more, not when the prompt got smarter. Giving it the live calendar, the memory and the open plans every turn, and making it ask before it acts, did more than any model upgrade.
AI changes the size of the team
With agents writing most of the code in parallel, my job moved to deciding what to build, reviewing every change and keeping the bar high. One person can now run what used to take a team, as long as the person stays the editor.