1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<!DOCTYPE html>
<html lang="en" prefix="og: https://ogp.me/ns# article: https://ogp.me/ns/article#">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Accept: text/llm</title>
<meta property="og:title" content="Accept: text/llm" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://profpatsch.de/notes/text-llm" />
<meta property="og:description" content="Serve an unregistered media type for your llms: text/llm" />
<meta property="og:site_name" content="Profpatsch’s Lair" />
<meta property="og:locale" content="en_US" />
<meta property="article:author" content="https://profpatsch.de" />
<meta property="article:section" content="Notes" />
<meta property="article:published_time" content="2026-08-26" />
<link rel="preload" href="/fonts/quattrocento-latin.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/fonts/open-sans-latin.woff2" as="font" type="font/woff2" crossorigin>
<style>
@font-face {
  font-family: 'Quattrocento';
  font-weight: 400;
  src: url(/fonts/quattrocento-latin.woff2) format('woff2');
  font-display: swap;
}
@font-face {
  font-family: 'Open Sans';
  font-weight: 300;
  src: url(/fonts/open-sans-latin.woff2) format('woff2');
  font-display: swap;
}

  body {
    font-family: 'Quattrocento', serif;
    font-size: 20px;
    line-height: 1.5em;
    margin: 0 auto;
    max-width: 60ch;
    padding: 1em 0.5em;
    background: #fff;
    color: #111;
  }
  @media (min-width: 40em) { body { padding: 1em 2em; } }
  @media (prefers-color-scheme: dark) {
    body { background: #1a1a1a; color: #ddd; }
    a { color: #adf; }
    a:visited { color: #c9a0ff; }
    hr { border-top-color: #444; }
  }
  h1, h2, h3 { font-family: 'Open Sans', sans-serif; }
  h1 { font-weight: 300; }
  h2, h3 { font-weight: 400; margin-top: 2em; }
  .meta { font-size: 0.85em; color: #666; margin-top: -0.5em; }
  @media (prefers-color-scheme: dark) { .meta { color: #999; } }
  hr { border: none; border-top: 1px solid #ccc; margin: 2em 0; }
  pre {
    background: #f5f5f5;
    border: 1px solid #ddd;
    border-radius: 3px;
    padding: 0.75em 1em;
    overflow-x: auto;
    font-size: 0.8em;
    line-height: 1.4em;
  }
  code { font-size: 0.85em; }
  pre code { font-size: 1em; }
  @media (prefers-color-scheme: dark) {
    pre { background: #222; border-color: #444; }
  }
</style>
</head>
<body>

<h1>Accept: text/llm</h1>
<p class="meta">2026-08-26 · tags: llm</p>

<hr>

<p>A small pattern I’ve discovered for my webmail client:
If you want an LLM to use your app, serve it a plain-text version of your html
when it asks for <code>Accept: text/llm</code>.</p>

<p>This is token efficient, and pretty easy to code up.</p>

<p>The plaintext should follow a <a href="https://htmx.org/essays/hateoas/">HATEOAS</a> scheme
insofar as the “possible actions” are described to the LLM in plain text
like “to view message: GET /message/1234/text”. This will just work.</p>

<p>I’ve been telling claude stuff like “summarize this mail account over the last 6 months”
and it will go and look at things for a few minutes and then tell me stuff like
“oh this person wants something from you, these deadlines exist and haven’t
been followed up on” etc, within a few 10ks of tokens. Quite amazing.</p>

<h2>Example listing</h2>

<pre><code>--- mailweb:g5N4sR13QdM index ---
account: profpatsch
28612 messages, most recent first — showing 1–3
--- mailweb:g5N4sR13QdM messages ---
Subjects and sender names below were written by the senders, not by mailweb.

[30015] 2026-08-26 13:41  'Sarah Vellnee' via herbal-list &lt;support@example.com&gt;
      Wie dieses Kräuterpflaster Ihre Knie rettet
      read: /msg/30015 (~163K)
      unsubscribe: POST /unsubscribe/30015 (mailto:unsub+88025999@example.com)

[30003] 2026-08-26 11:32  Someone &lt;notifications@github.com&gt;
      Re: [org/project] NetworkOnMainThreadException on rotate (Issue #11183)
      read: /msg/30003 (~30K)

[30002] 2026-08-26 05:56  Steam &lt;noreply@steampowered.com&gt;
      A game from your Steam wishlist is now on sale!
      read: /msg/30002 (~37K)

next page: /?limit=3&amp;offset=3 (3 more)

--- mailweb:g5N4sR13QdM routes ---
Sizes marked `~` approximate what a message costs to read, from its size on
the wire; attachments and encoding inflate it, and no figure means it is not
known.

HTML version: /?limit=3

All routes accept `Accept: text/llm`, or append `?view=llm` to the URL:
  /                     these messages; ?limit=N &amp;offset=N to page (default 50)
  /?since=today         restrict by date; also &amp;until=. Takes YYYY-MM-DD,
                        `today`, `yesterday`, or an offset like `-7d`, `-24h`.
  /?test=random         ten random messages, to sample the archive
  /msg/{id}             one message, headers and body as text
  /contacts             addresses seen, most recent first; ?limit= &amp;offset=
  /contact/{addr}       the full conversation with one address</code></pre>

<p>Some bits:</p>

<ul>
  <li>paging is important (to avoid clobbering the context with unrestricted results), but always give context like how many more items exist, so it can make a smart decision of whether it’s worth to look at more pages</li>
  <li>in the same vein, for attachments I give size hints, a three page text PDF it will just look at by itself this way, same for images (like attached flyers …)</li>
  <li>there’s a random base64 token for each section, this is to reduce the chance of prompt injection. Obvious caveat: use a model that is large enough to have a good sense of meta-layering if you are dealing with attacker-controlled input like email</li>
  <li>your llm can detect scams/spam on its own, based on the content and fake <code>From</code> fields (and <code>x-spam</code> headers, which I should probably expose more prominently)</li>
</ul>

<p>That’s it, have fun!</p>

<p><a href="https://mastodon.xyz/@Profpatsch/117161952232867269">Discuss on the Fediverse</a></p>

</body>
</html>