From ai-plugin.json to SKILL.md

I shipped the same email-auth tool four times: as the SaaS emailauth.app, as a ChatGPT plugin in April 2023, as two Custom GPTs in late 2023, and now as a Claude Code skill. Each iteration deleted a layer of the stack. The agentic side won. The new customer is the agent, and the agent does not subscribe.

The earlier articles in this series (1, 2, 3, 4) argued that the buyer is changing, the product surface is changing, and the pricing model is changing. This piece is the receipts. The same small product, an email-authentication auditor, has existed in my portfolio in four shapes: first as a SaaS, then as a ChatGPT plugin, then as a pair of Custom GPTs, now as a Claude Code skill. Each new shape was triggered by a new integration layer underneath, and each new layer kept collapsing the one before it. I want to walk through the four shipping cycles with the actual filenames, because the filenames are the story.

Origin · SaaS

SaaS since 2022
emailauth.app

React front-end on a Cloudflare Worker. Type a domain, get a score across SPF, DKIM, DMARC and BIMI, with a history, an FAQ and a share link.

emailauth.app Open

Before any of the AI-side shapes there was a SaaS. The page is built around DNS lookups, parsed against the RFCs, scored, painted into a results panel for a human visitor. It has a homepage, a /faq, a /privacy, a saved history in localStorage, a copyable share link. A normal product for a normal human visitor.

The hard part of building it was never the parsing. The parsing was maybe 600 lines of TypeScript. The hard part was every layer around it: the React routes, the auth, the rate limiter, the marketing page, the FAQ explaining what DMARC is to a CTO who searched the term on Google. The job was wrapped in a SaaS because in the pre-AI world a SaaS was the only way a piece of expertise reached someone who needed it. You had to publish a URL and hope a human visited.

Then the integration layer underneath started to change, and each change shed one of those wrapping layers.

April 2023 · ai-plugin.json

ChatGPT Plugin Apr 2023
domain-tools-chatgpt-plugin

The same job, two weeks after OpenAI announced plugins. Manifest, OpenAPI spec, FastAPI backend, Dockerfile. A microservice the agent's vendor would phone home to.

github.com/emresavas ai-plugin.json Open

OpenAI announced ChatGPT plugins on March 23, 2023. Two weeks later I pushed this repo. The createdAt field is 2023-04-06T19:04:03Z. It contains, in order of pain:

ai-plugin.json       # the plugin manifest OpenAI's fetcher would hit
openapi.yaml         # the OpenAPI 3.0 spec describing every endpoint
main.py              # a FastAPI backend doing the actual work
Dockerfile
docker-compose.yml
requirements.txt
.env_example
README.md

To ship this plugin, the agent’s vendor (OpenAI) needed to read a manifest, fetch and parse an OpenAPI spec, present the human user with an installation prompt, store the consent, route every tool call back to my public URL with auth headers, and depend on me to keep that URL alive. I, in turn, needed a server, a domain, a TLS cert, a Docker setup, a logging pipeline, an SLO I could keep, and a plugin-store submission OpenAI would approve. The “tool” was maybe 200 lines of Python. Everything else was wrapper.

The good part of this era was that the wrapper was unambiguous: there was a single canonical manifest format and a single client. The bad part was that the wrapper was all I did. Adding a new endpoint meant edit main.py, edit openapi.yaml, redeploy, hope OpenAI’s fetcher re-pulled the spec. A typo in openapi.yaml was a production outage.

This was the first time it was possible at all to put a tool inside a model. The price was running a microservice for it.

November 2023 · Custom GPTs

Custom GPT Nov 2023
Email Authentication & Deliverability

System prompt plus an OpenAPI Action pasted into a textarea. No repo, no manifest, no plugin-store queue. The whole tool became a row in OpenAI's database.

chatgpt.com Open
Custom GPT Nov 2023
DNS Lookup

A sibling Custom GPT for DNS lookups. Same shape: textarea, Action, avatar in a file picker. Same constraint: locked to chatgpt.com.

chatgpt.com Open

At OpenAI DevDay on November 6, 2023, plugins were de facto replaced by Custom GPTs. The store opened. The friction dropped through the floor. The system prompt was a textarea, the tool was an “Action” pasted as OpenAPI in a form field, the avatar was uploaded in a file picker. I still needed the backend behind the Action, but I no longer needed the manifest dance, the store submission, the consent dialog, or even Git.

This was the moment I stopped writing ai-plugin.json files entirely. One layer of the stack disappeared in eight months. The job stayed the same: take a domain, return its email-auth posture. The integration cost fell from “stand up a microservice and a submission” to “fill in a form.”

The bad part of this era was that the wrapper had migrated into the vendor’s UI. My GPT lived inside chatgpt.com. If OpenAI rolled it forward, fine; if they didn’t, fine. There was no portability. If I wanted a Claude or Gemini version, I started from scratch in their UI, with their fields.

2026 · SKILL.md

Claude Code Skill May 2026
emresavas/claude-code-plugins

One Markdown file. No backend, no API key, no rate limit, no submission queue, no vendor UI. The model in the user's terminal runs the lookups itself, against its own resolver.

github.com/emresavas SKILL.md Open

The thing I shipped today is one Markdown file long. The full structure of the plugin is:

email-auth/
├── .claude-plugin/plugin.json
└── skills/
    └── check-email-auth/
        └── SKILL.md

That is the entire artifact. The SKILL.md has YAML frontmatter declaring when the skill should trigger, and a body that teaches the agent which dig and curl commands to run and how to interpret the output. There is no backend. There is no server. The model itself, sitting in the user’s terminal, runs the lookups against whatever resolver the user has.

---
name: check-email-auth
description: Audit a domain's email authentication posture. Covers SPF,
  DKIM, DMARC, BIMI, MTA-STS, TLS-RPT and DNSSEC. Trigger when the user
  asks about email deliverability, DMARC records, SPF lookups, DKIM
  selectors, BIMI logos, MTA-STS, or anything resembling "is my email
  setup correct" for a domain.
---

Two install commands:

/plugin marketplace add emresavas/claude-code-plugins
/plugin install email-auth@emresavas

That is the whole shipping cycle. The repo is on GitHub, public, MIT, one commit. There is no second component anywhere. Side by side:

SaaS
emailauth.app
Manifest
None (it is the app)
Tool body
Cloudflare Worker
Hosting
Yes
Reached by
Human via URL
Install UX
Visit a website
Portability
The web
Ship a fix
Deploy the Worker
ChatGPT Plugin
Apr 2023
Manifest
ai-plugin.json + openapi.yaml
Tool body
FastAPI service
Hosting
Yes (Docker)
Reached by
OpenAI + a human installer
Install UX
Plugin store approval
Portability
OpenAI only
Ship a fix
Edit, deploy, wait for refetch
Custom GPT
Nov 2023
Manifest
OpenAPI in a textarea
Tool body
FastAPI service
Hosting
Yes (Action backend)
Reached by
OpenAI + a human installer
Install UX
Custom GPT row
Portability
OpenAI only
Ship a fix
Edit form field, save
Claude Skill
May 2026
Manifest
plugin.json
Tool body
SKILL.md (instruction)
Hosting
No
Reached by
Any agent with the plugin runtime
Install UX
/plugin install
Portability
Anywhere with the Claude Code plugin runtime
Ship a fix
Edit Markdown, push

Four iterations, same job. Each iteration deleted a layer.

Why the layer keeps collapsing

The technical reason is that the model got better at executing tools and the runtime got better at exposing them.

In 2023 the model could not be trusted to issue arbitrary commands; the safe move was to constrain it to a fixed API surface. So the surface was a microservice with a known OpenAPI spec. The vendor’s job was to mediate every call. That is the world ai-plugin.json and the GPT Actions schema were designed for.

In 2026 the model in your terminal already has shell access through its agent runtime. It already has dig, host, curl, jq, whois. Giving it a tool is no longer “set up a network endpoint and document it”; giving it a tool is “tell it the right commands to run.” The instruction is the tool. SKILL.md is just the place where the instruction lives.

This changes the economics in three specific ways that matter for anyone building one of these things:

  1. No HTTP round trip. When the SaaS answers, the answer is JSON behind a network boundary. The agent has to call out, wait, parse, re-enter its own context with the result. With the skill, the answer is text already inside the same conversation. The next reasoning step references it directly. The reasoning surface and the tool surface are the same surface, which is the point the tool-calling article was making in the abstract and which is the entire reason the layer keeps shedding in practice.

  2. The description field is more powerful than the endpoint path. A SaaS endpoint named /api/v1/dmarc/check is matched by a brittle URL string and a fixed parameter schema. The description: on a skill is prose the model reads in context, so the trigger condition can be semantic (“the user is asking whether their email is set up correctly, in any phrasing, including informal ones”). That is a far stronger contract for an LLM than any URL has ever been.

  3. No subscription seat to occupy. Per-seat pricing assumed there was a seat. The agent installing this skill is not occupying a seat; it is reading a file on disk. The per-seat article called this from the demand side; the supply side is what you are reading now.

The harder thing nobody is saying out loud

This is where I deviate from the “both still exist, pick the right tool for the job” softness that you usually hear in this kind of article.

The agentic side won.

The traditional SaaS business is over for tools at this scale. emailauth.app will stay up, because it is cheap to run and someone might still pay for the dashboard. But I am not building features for it anymore. The energy goes into the skill. Every tool-shaped product in my portfolio, a DNS lookup API, a WHOIS API, a DMARC parser API, an email intelligence API, an email deliverability API, the SERP APIs, every one of these built a small business around a thin layer of expertise on top of public data. Every one of them is being eaten in real time by the equivalent of a 300-line Markdown file installed by a single command.

The reason is not that the SaaS got bad. The SaaS is fine. The reason is the buyer changed. The first article in this series said it plainly: the consumer profile changed. A SaaS dashboard exists to give a human a place to read a score, copy a record, send a teammate a link. The agent does not need any of those things. The agent needs the result, in text, where it is already thinking. Everything else is gift wrap.

The buyer is the agent now. The agent does not subscribe. The agent does not click. The agent does not value your homepage, your onboarding, your branded PDF report, your /faq, your /privacy. The agent values: can I run this in my own context, with my own resolver, at my own latency, without an API key? If yes, the agent uses you. If no, the agent uses the next one.

The dashboard is the milkshake; I wrote about this earlier. The skill is what the agent buys instead, except “buys” is the wrong verb because there is no transaction. The skill is what the agent installs. The economics around what a tool is, who pays for it, and how it gets distributed are being rewritten in front of us, and the people running thin SaaS layers on top of public data are not going to like the next two years.

I have built six or seven of these myself. I am not making a prediction; I am telling you what I am doing about my own portfolio. The plan is to collapse every one of them into a skill, in this repo, in this shape, one at a time. The first is shipped.

The first one

emresavas/claude-code-plugins, plugin email-auth, skill check-email-auth.

What it does: takes a domain. Returns a structured Markdown report with PASS / WARN / FAIL per protocol (SPF, DKIM, DMARC, BIMI, MTA-STS, TLS-RPT, DNSSEC), a remediation section ordered by impact, and the raw records appended so the user can verify.

The non-obvious technical bits inside the SKILL.md:

  • SPF lookup counter. The skill walks include: chains recursively and counts each lookup against the RFC 7208 §4.6.4 limit of 10. Receiving MTAs will return PermError above that. Most SaaS auditors silently truncate; this one fails the audit and tells you which include pushed you over.
  • DKIM selector probe list. DKIM has no canonical record location, so I curated 24 selectors that cover Google Workspace, Microsoft 365, Fastmail, SparkPost, SendGrid, Mailgun, Mailchimp Transactional, Postmark, SocketLabs, Kinsta, Zoho, Proton, and the common legacy rotation names. The skill probes the list and reports both hits and tried-but-missing, so the user can see what was actually checked.
  • DMARC severity ladder. Not a yes/no. Five rungs: missing > p=none > p=quarantine; pct<100 > p=quarantine; pct=100 > p=reject. BIMI is reported N/A rather than FAIL when DMARC is below quarantine, because BIMI literally cannot apply at that policy level and calling it a failure is misleading.
  • External rua= authorization check. If the DMARC rua mailbox is on a different domain, the skill does the second-order lookup at <audited>._report._dmarc.<reporter> to verify the reporter authorization, because most reporters silently drop unauthorized reports and most auditors do not check.

Install:

/plugin marketplace add emresavas/claude-code-plugins
/plugin install email-auth@emresavas

Pin the repo or do not. Open issues for weird domains. The DKIM selector list in particular is going to need a long tail of pull requests from anyone running mail through a provider I have not personally used.

The next plugins in this repo will be shaped the same way. Pure instruction. No service. No key. Each one removes a wrapper, the same way the manifest got removed, the same way the Custom GPT form got removed, until what is left is the actual expertise, written down once, runnable by an agent on a laptop with no network call to me.

The wrapper is the thing that goes away. The job stays. The receipts are in four shapes: a SaaS dashboard, an ai-plugin.json manifest, an OpenAPI textarea, a SKILL.md file. In that order, each smaller than the last.