This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
What I Built
Someone I know is starting out in UGC (user-generated content) for pet brands. She plans to pitch brands directly, and she described it as sending a resume to employers in a field that's completely new to her.
That stuck with me. A pitch from a beginner has to do three things at once: sound human, fit the brand, and be truthful.
A beginner might not have a big audience or a long list of brand deals to show. An AI that politely invents either one isn't helping. It's creating a liability.
So I built PawPitch, a small Rust command-line tool with two jobs:
Draft pitches. The creator keeps a private JSON profile of facts about herself, her pet, and the content she can produce. For each brand, she supplies its description. A local AI model drafts an email or DM, and Rust checks for specific kinds of unsupported claims.
Track follow-ups. PawPitch logs who was contacted, when, and through which channel, then lists outreach that is due for a follow-up after a week.
The creator reviews and sends every message herself. PawPitch never sends anything automatically
Demo
Everything in the demo uses a made-up creator (a beagle called Biscuit) and made-up brands. A real creator's facts never leave her own machine.
Code
PawPitch
A small local tool for new UGC (user-generated content) pet creators. It drafts outreach messages to pet brands with a local open-weight model (Gemma 3 4B via Ollama), checks every claim against the creator's own facts, and keeps a simple follow-up tracker.
The idea: the model drafts, the code checks
- Your facts live in
data/profile.json. Empty fields stay empty. - For a brand, paste its description (or a gig post) into a text file.
- Gemma drafts an email or DM and cites the facts it used. Brand sentences must be copied exactly; profile citations are advisory, because a small model paraphrases.
- Rust rejects the draft if
- a cited phrase is not word for word in the sources;
- it contains a number that is in neither source;
- it claims followers, views or past brand partnerships, or equipment and experience your profile does not list;
- it uses he or she for…
How I Built It
- Model: Gemma 3 4B through Ollama, on my laptop's CPU. No GPU, no cloud API.
- The facts live in one JSON file. Empty fields stay empty, so the model can't quote them.
-
The model drafts, the code checks. The prompt asks for four short sentences and forbids inventing facts. Then Rust rejects a draft that:
- contains a number that is in neither the creator's facts nor the brand text;
- claims followers, views, past brand deals, equipment or experience the creator hasn't listed;
- calls the pet "she" or "he" when the profile doesn't say which;
- names a content format she doesn't offer (a profile that says "unboxing photos" cannot become "unboxing videos");
- says the pet has already tried or loved the brand's product, instead of proposing the idea;
- calls the creator or pet something like "mom" or "owner" that the profile doesn't state.
- Soft words become warnings. Words that appear nowhere in the facts or the brand text ("fantastic", "enthusiastically") are listed under "Double-check before sending", so a human sees exactly where invention can hide.
- A rejected draft is regenerated with a new seed, up to five times, and every rejection prints its reasons.
- The subject line is written by the code, so the model can't embellish it.
- The tracker is plain Rust and JSON. There are 32 unit tests.
What Went Wrong
I thought the prompt would be the hard part. It was the checker, and I got it wrong in both directions.
- The first checker let bad drafts through. It only verified that the facts the model cited existed in the profile. The first real draft cited valid facts and still gave the dog a gender the profile never stated, turned "unboxing photos" into "unboxing videos", and said the dog was especially fond of the brand's treats. All three passed.
- A second draft passed with invented personality. It called the creator a "beagle mom" and said the dog was "always ready for a new adventure". Neither is in the facts.
- Then I over-corrected. After I made personality words a hard rejection, [9] attempts in a row were rejected, even though the drafts were decent. A tool that refuses everything isn't a tool. I turned soft personality words into warnings and kept hard rejections for things that could actually mislead a brand.
- Then I found a plain bug. [15] more attempts were rejected because the profile says "slice-of-life videos" and the model wrote "slice-of-life video". My check treated singular and plural as different formats. A passing draft appeared as soon as I fixed it.
- The display overstated what was used. The tool listed "dramatic when the treat jar is empty" as a fact in the message because one word, "treat", matched. For a tool built to avoid overclaiming, that was embarrassing. It now requires every meaningful word of a fact to appear.
The lesson I'd keep: a checker needs real examples of failures, and the failures should become tests. Eight of the 32 tests replay actual drafts the model wrote.
Does It Work? A Small Evaluation
I couldn't test it on the person I built it for before the deadline, so I measured what I could. ./eval/run.sh runs a fictional creator against five fictional brands, with up to five attempts each, and reports whether a draft passed the fact check.
All five brands produced a passing draft: four on the first attempt, and Fetch and Fern on the second.
I then read all five drafts myself. [What you found: for example "none contained a claim I could not trace to the profile or the brand text", or describe any claim that slipped through and what you did about it.] Each draft also came with a "Double-check before sending" list of words that appear in neither the creator's facts nor the brand text, such as [one or two examples from your output].
A few caveats:
- It's five brands and one fictional creator whose profile I wrote myself. That isn't a study.
- I developed most of the checker against one of the five brands (the first one), so that result is the least independent. The other four are a fairer test, but they're still few.
- Passing means the draft avoided the kinds of invention I could list and test for. It doesn't prove every word is true, which is why the warnings exist and why the creator reads every message before sending.
- Re-running can differ. Even with a fixed seed, I saw the first draft in a session occasionally differ from later ones.
- Each attempt takes about [N] seconds on my laptop's CPU, so a brand can take a few minutes. That's slow, and it's the first thing I'd improve.
Passing doesn't prove every word is true. It means the draft avoided the specific kinds of invention I could list and test for. The "Double-check" warnings are there for everything else, and the creator must read every message before sending.
Would Anyone Pay for This?
I don't know yet, and I'd rather say so than invent an answer.
The person I built it for agreed to try it but hasn't had time before the deadline, so I can't report how she used it, what she changed, whether she'd pay, or what she'd ask for next. Here's what I'd ask her: Did a draft say anything untrue or uncomfortable? How much did you edit it? Did it save time compared with writing from scratch? What have you paid for before that helped with outreach, and what would make this worth paying for?
What I can offer instead are hypotheses, not findings:
- The value isn't speed alone, since a general chatbot also writes pitches. It's a draft that refuses to claim what a beginner doesn't have, which protects her first impression with a brand.
- The cost per pitch is only electricity, which matters because a creator with no income yet shouldn't need a subscription.
- The risk is that beginners have little money, so the buyer may be someone else: a UGC coach or community that gives it to new members.
I haven't tested any of that, and I haven't compared PawPitch against a general chatbot.
Why Does Open Innovation Matter?
- Cost. There are no per-pitch fees. For someone who hasn't earned anything yet, a metered API is a bill before the first reply.
- Privacy. A creator's facts, her list of brands and her outreach history stay on her own machine. None of it goes to a third-party AI service.
- Control. Because the model is one replaceable part, I could change the prompt, fix the seed, and wrap it in checks. The checks did more for trust than any change to the model.
- What I didn't test: other open models. I used only Gemma 3 4B, and trying others on the same brands is the obvious next experiment.
What's Next
A gig-post checker (pay, usage rights, red flags, with quotes from the post), scheduled follow-up reminders, and a real session with a real creator.
Prize Categories
Best Use of Gemma

Top comments (0)