Postmark
September 20, 2026 ยท View on GitHub

Postmark is a tiny web app that reads your draft social post before you publish it and tells you how it might land.
You paste in some text, hit one button, and it comes back with three honest reads: what tone you are giving off, how far it might travel, and whether it might make people cringe a little. It is meant to feel like asking a blunt friend, except the feedback comes back as clean numbers and bars you can actually use in code.
I built it as a demo for the TypeSafe Jev model, to show what happens when a model returns structured answers instead of a paragraph you have to parse.
What it feels like to use
You open the page and see a draft card that looks like a stamped piece of paper.
- Paste your post into the box. Anything up to 2000 characters.
- Press Vibe check.
- On the right you get three cards plus a big verdict stamp on top.
The verdict is simple on purpose. These are the exact labels you will see in the UI:
Hold up. Its giving cringe: the cringe score came back at 0.6 or higher. Probably worth a rewrite.Send it. Its giving viral: cringe looks fine and the virality score is 2.2 or higher.You ate. Clear to post: cringe looks fine and virality is normal. Nothing alarming.
Under the verdict you get the detail:
Vibe This is a Choice answer. The model picks one dominant tone from a fixed list and also returns a probability for every option, plus an overall confidence number. The options right now are hot take, humble brag, inspirational, informative, funny, and vulnerable.
Viral potential This is a Score answer. The model places your post on a 0 to 3 scale and returns a probability for each level, plus confidence. The levels are Ignored, Mild, Solid traction, and Breakout. The UI shows the label for the rounded score, so a 2.3 shows up as Solid traction territory.
Cringe meter
This is a Noul answer, which is basically a 0 to 1 likelihood. The UI shows it as a single percentage bar with the label sus level. Higher means a thoughtful reader is more likely to wince.
All three run in parallel from one API call. You do not have to chain prompts or parse JSON out of chat text.
Try it without typing
There are four sample buttons under the draft box:
- A contrarian take about remote work and mentorship.
- A personal win post about getting laid off and later signing an office lease.
- A plain public service note about library hours. This one is intentionally boring, so you can see what low virality looks like.
- A long emotional fundraising style post written at 3am after closing a Series A. This one is useful for seeing high emotion plus mixed cringe signals.
Click any sample and it fills the textarea for you.
Run it locally
You need Node 20 or newer and a TypeSafe API key.
npm install
cp .env.example .env.local
Open .env.local and add your key:
TYPESAFE_API_KEY=your_key_here
Then:
npm run dev
Open http://localhost:3000 in your browser.
Other commands:
npm run lint
npm run build
npm start
Build is a standard Next.js production build. Lint is ESLint with the Next config. There is one known warning about an unused function argument in the API route, which is harmless.
How it works under the hood
There are really only two important files.
app/page.tsx
This is a client component and it owns all the UI state.
draft,loading,error, andresultlive inuseState.charCountandoverLimitare derived on every keystroke. The limit is 2000 characters for the API, while the textarea allows 2200 so you can see the over limit message before you get blocked.runInspectionposts{ draft }to/api/inspect, handles HTTP errors, and stores the typed response.verdictis auseMemothat readsresult.answers.virality.scoreandresult.answers.cringe_risk.nouland applies the two thresholds described above. No model call here, just plain if else logic.- Results render as probability bars. Tone bars are sorted high to low. Virality bars are sorted by level 0 to 3. Widths are simple percent values from the model probabilities.
The page does not talk to TypeSafe directly. That keeps the API key off the client.
app/api/inspect/route.ts
This is the only server side piece. It does a small amount of validation and then forwards to TypeSafe.
Request flow:
- Read
TYPESAFE_API_KEYfrom the server environment. If it is missing, return a 500 with a friendly message about adding it to.env.local. - Parse the JSON body and pull out
draftas a trimmed string. Empty or missing draft returns 400. - If the draft is longer than 2000 characters, return 400.
- Build the payload:
{
"state": "your draft text",
"model": "jev-latest",
"questions": {
"tone": {},
"virality": {},
"cringe_risk": {}
}
}
- POST it to
https://api.typesafe.ai/v1/systemonewith a Bearer token and JSON headers. - Pass through rate limit statuses 429 and 529 as is, map other upstream failures to 502, and on success return the model JSON straight to the client.
The three questions are defined in buildQuestions:
- Tone is type
choicewith six named criteria and a short plain language description for each. - Virality is type
scorewith four ordered criteria from Ignored to Breakout. - Cringe risk is type
noulwith true meaning tone deaf or awkward and false meaning natural and comfortable.
Because Jev returns typed answers, the frontend can trust the shape: choice plus probabilities plus confidence, score plus legend plus probabilities plus confidence, and noul as a number. No regex, no JSON repair step.
app/layout.tsx and app/globals.css
Layout sets the page title and description and loads two Google fonts: Special Elite for stamp style headings and IBM Plex Sans for body text. The visual theme is paper and ink, with a dotted paper texture, hard borders, and little rotated verdict stamps.
The CSS is hand written, no component library. The main layout is one column on phones and two columns at 860px and wider. There are extra breakpoints at 640px and 480px for tighter padding, stacked footers, full width buttons, and narrower probability bars so nothing overflows on a 320px screen. Textareas stay at 16px font size so iOS does not zoom in while typing.
Project structure
app/
page.tsx Main UI, samples, verdict logic, result cards
layout.tsx Title, description, fonts
globals.css Paper theme plus responsive rules
api/
inspect/
route.ts Validation plus TypeSafe SystemOne call
public/ Static assets
.env.example Placeholder for TYPESAFE_API_KEY
next.config.ts Default Next config
package.json Next 16, React 19, TypeScript
The lib folder exists but is empty right now. Good place for shared helpers if this grows.
Limits and edge cases
- Drafts must be non empty after trimming and 2000 characters or less. The UI disables the button and shows a counter message when you go over.
- If TypeSafe is down or the key is wrong, you get the error text in a red note under the draft box instead of a silent failure.
- Verdict thresholds are deliberately simple and live in the client so you can tweak them fast. If 0.6 cringe feels too strict for your audience, change one number.
- Tone labels come straight from the model, so if TypeSafe adds or renames a tone, the UI will show it without a code change.
Why structured answers help here
A normal chat model would reply with something like Sure, this post feels inspirational with viral potential. Then you would have to parse that sentence and hope the wording never changes.
Here you get fields you can rely on. You can sort probabilities, draw bars, compute a verdict, log scores, or gate a publish button, all without string parsing. That is the whole point of the demo.
Postmark is one example. Jev can do a lot more
Postmark shows a single Jev use case: content checking with one Choice, one Score, and one Noul in parallel. The same idea applies anywhere your app needs a fast judgment call instead of a paragraph.
It helps to see why Jev exists in the first place and where normal chat models start to feel awkward.
Where normal LLMs feel heavy
Most chat models work the same way. You give them input, they reason, and then they generate tokens one after another until you get text back. If you need a decision for software, you then have to parse that text, validate it against a schema, handle the cases where it drifts, and pay for all the output tokens.
That flow is fine when you actually want writing, code, or conversation. It gets painful when all you needed was three values like queue equals billing, priority equals high, and refund review equals true.
The pain shows up in four places:
Speed Token by token generation takes seconds, especially for reasoning models. If you call that inside a loop or for every user action, the app feels slow.
Cost You pay for input plus output. Long explanations that you will throw away still cost money.
Reliability Even with JSON mode, the model is still generating text that has to fit your schema. It can rename a field, invent a new label, or add extra commentary. You end up writing repair code.
Overkill Many tasks are simple decisions. Should we retry. Should we escalate. Which queue gets this ticket. Does this answer meet the requirement. You do not need an essay to answer those.
What Jev does differently
Jev is built by TypeSafe as a System One model. The name is a nod to fast thinking versus slow thinking. Slow thinking is for hard open ended work. Fast thinking is for quick calls your software makes all day.
The interface is different:
State plus questions in, typed decisions out.
You send application state, like a draft post or a support ticket, plus a set of typed questions. Jev returns decisions with probabilities and confidence, not paragraphs. The three building blocks are:
- Choice: pick one option from a fixed set. Good for classification, routing, tool selection, workflow branching.
- Score: place something on an ordered scale. Good for urgency, risk, quality, virality.
- Noul: a yes or no likelihood between 0 and 1. Good for questions like was a refund issued or is this risky.
Postmark uses all three at once from the same draft. That parallel part matters. Instead of three separate model calls, Jev evaluates the questions together in one pass. TypeSafe reports 70 to 500 ms end to end in their service, with vendor reported workflow wins up to about 193 times faster and 444 times cheaper on System One shaped tasks compared to frontier LLM workflows in their own evaluation. Treat those as vendor numbers for that kind of workload, not a universal claim, but the direction is right because there is no long token stream to generate. Output is free in their pricing and input is listed around 0.042 dollars per million tokens.
Two other details make it practical:
Calibrated confidence Every decision comes with a sense of how sure the model is. That lets you write real rules. Above 0.90, act automatically. Below that, send to a human. Postmark does a tiny version of this with its 0.6 cringe and 2.2 virality cutoffs.
Type safety by design With Choice, the model can only pick from the options you declared. It cannot invent a brand new queue name outside your list. It can still pick the wrong option, but it stays inside the shape your code expects. No schema repair step.
Jev is not trying to replace ChatGPT, Claude, or Gemini for writing, coding, or chat. It sits next to them as a decision layer.
Other use cases worth trying
If you like what Postmark does, here are natural next experiments with the same pattern.
Agent control loops Let a big model do the hard reasoning, let Jev make the fast calls around it. Should the agent retry. Should it call another tool. Should it ask the user. Should it stop. Which sub agent should run next. Those are all Choice or Noul questions and they need to be cheap and quick.
Tool and route selection Given user intent plus app state, pick billing versus technical versus shipping, or pick which function to call next. Typed Choice keeps routing stable.
Verification layer for LLM output Generate with a chat model, check with Jev. Is the answer supported by context. Is it safe. Does it follow the requested format. Should we send it or hold for review. This accept, reject, or human review pattern is one of the most useful setups for production AI.
Support triage Urgency as Score, department as Choice, refund needed as Noul, all from the same ticket. High urgency goes to a human now, low urgency gets an auto reply.
Risk and fraud scoring Score for how risky an action looks, Noul for should we step up verification. Low latency matters here because these checks run on every transaction.
Guardrails and moderation Noul for policy violation likelihood, Choice for violation category, Score for severity. Because outputs stay in schema, you can wire them straight to allow, warn, or block.
Realtime personalization Which recommendation to show, which onboarding variant fits, which message tone matches this user. Small fast decisions beat a slow chat call on every page load.
Workflow automation Approve or escalate an expense, route a document, decide if a form is complete. Boring work is often just a chain of small typed decisions, which is exactly what this model shape is for.
If you build any of these, the Postmark code is a good starting template. Copy the API route, swap in your own questions and criteria, keep the threshold logic in plain code, and render probabilities however fits your UI.
Where this kind of classifier is useful
Postmark is a small demo, but the pattern behind it shows up in a lot of real products. Any time you want a fast read on text and a number you can act on, this setup fits.
For people who post a lot Founders, creators, social media managers, and job seekers can check a draft before it goes live. You catch a tone that does not match your intent, you spot a post that reads as bragging when you meant it as grateful, and you decide whether to soften it, split it into two posts, or just post it as is.
For teams that publish together If three people write for one company account, a shared checker keeps the voice steady. You can set your own tone list, like announcement versus story versus opinion, and flag anything that drifts. New teammates get feedback in seconds instead of waiting on a review thread.
For community and trust work The same shape works for triage. A Choice question can route a report to spam, harassment, or content question. A Score question can rank urgency from ignore to needs a human now. A Noul question can estimate likelihood, like how likely is this message to upset a reader. Because the output is typed, you can send high scores straight to a review queue and let low scores through.
For product and research loops If you run a lot of copy, like onboarding screens, emails, or landing pages, you can score variants before an A and B test. Pick the two strongest tones, cut the weakest, and then test with real users. You save a test cycle and you have the scores logged for later.
For learning what your audience likes Since every answer includes probabilities and confidence, you can store results over time and look for patterns. Maybe funny posts with low cringe travel best for you, while hot takes get reach but also high cringe. That kind of log is much easier to analyze than a pile of chat replies.
What maps to each Jev type Choice fits when you need one label from a fixed set, like tone, topic, intent, or priority bucket. Score fits when you need a rank on a scale, like quality from 0 to 4, urgency, or virality. Noul fits when you need a simple likelihood between 0 and 1, like cringe risk, confusion risk, or churn risk. Postmark uses one of each so you can see all three in a single call.
Ideas if you want to take it further
- Save a history of inspections so people can compare drafts.
- Let users edit the tone list or add their own house style.
- Add a shareable result link.
- Track which verdicts people agree with and tune the thresholds.
- Add tests around the verdict logic, since that is pure and easy to cover.
If you are just exploring, start with the samples, watch how the bars move between different posts, and then paste in one of your own drafts.