← All field notes
IPP

Someone built an IPP printer out of an e-ink reader. Here is what "driverless" actually means

A developer turned an e-ink reader into a printer that macOS discovers on its own, with no driver. The stack that makes that work is three things, and it explains why raw label printing is a different problem.

"Driverless" printing is three things: an mDNS announcement, IPP over HTTP, and a raster format the host can produce. A microcontroller can implement all three, which is the clearest possible demonstration of how small the stack is, and of why the label printers that run e-commerce sit outside it.

On September 8, 2026, Nishant Joshi posted How to build a printer, which reached 444 points on Hacker News. He turned an Xteink X3 e-ink reader, an ESP32-C3 with about 400 KB of RAM, into a printer that macOS finds and prints to with no driver installed. The page arrives on the e-ink panel. His framing: "If it looks like paper, it should act like paper."

Implement a discovery announcement, a transport protocol, and a raster format, and a modern OS treats you as a printer. This post explains the three, then explains why label printers mostly sit outside that stack, and what a print API has to do about it.

Driverless means the host does the rendering and the printer accepts raster

A printer driver exists to translate. The application produces a document; the driver knows the printer's language and its quirks, and turns pages into whatever bytes that specific model wants. That knowledge is what makes drivers vendor-specific, fragile, and, on Windows, the thing Microsoft is now retiring in favor of an inbox class driver (Windows Protected Print).

The driverless model removes the translation by fixing the language. The host rasterizes the page itself into a standard bitmap format. The printer accepts that bitmap. Neither side needs model-specific code, because the contract is the format, not the device. The e-ink project works because it accepts exactly the formats macOS already knows how to produce.

Discovery is one mDNS record with the right subtype

Before a computer can print to something, it has to know the something exists. Driverless printers announce themselves on the local network with multicast DNS, the same mechanism Bonjour uses for everything else. Joshi's device advertises an _ipp._tcp service, and the detail that makes macOS treat it as driverless is the _universal subtype on that announcement. Without the subtype, the OS sees a printer it might need a driver for. With it, the OS assumes the printer speaks the standard raster formats and offers it immediately.

That is the whole discovery story: one service record, one subtype, and a hostname the OS can reach. It is also why a hand-built device can appear in the print dialog on its own, while a label printer that needs a vendor driver appears only after somebody installs one.

IPP is operations over HTTP, and Print-Job carries the bytes

The Internet Printing Protocol runs over HTTP. RFC 8011 defines the model and semantics, including the operations and the attributes they carry; its companion RFC 8010 defines the binary encoding and the HTTP transport, where each operation is an HTTP POST whose body holds an operation code, a set of attribute groups, and optionally the document data. The two operations Joshi implemented are the two you need for a minimal printer: Get-Printer-Attributes, which answers the host's question about what the printer supports (formats, sizes, resolutions), and Print-Job, which delivers a document with its attributes and then the document bytes.

The attribute exchange is what replaces the driver. The host asks what formats you accept; you say Apple Raster and PWG Raster; the host renders to one of those and sends it. There is no negotiation about ZPL or PCL or any vendor language because the host never needs to know one.

If you have written a small HTTP server, you have most of an IPP printer. The remaining work is the binary attribute encoding and the raster decoder, which is where the interesting engineering in the post lives.

The raster formats are why memory becomes the problem

Apple Raster and PWG Raster are page bitmaps with a header in front of each page. PWG 5102.4 specifies the PWG Raster side: a synchronization word, then per page a header followed by the bitmap, compressed with a PackBits-like scheme that keeps parsing cheap on a small device. Cheap to parse is not the same as small. Joshi's post gives the number: a Letter page at 300 dpi is about 8.4 MB uncompressed. His device has around 400 KB of RAM in total.

The solution in the post is the one an embedded engineer reaches for: never hold the page. Stream the rows as they arrive, decode each, downscale it to the display's resolution, dither it to black and white, and write it straight into the display's frame buffer. The intermediate image buffer went from about 113 KB to about 62 KB after that change. The page is rendered by the time the last row is received, and there was never a whole page in memory anywhere on the device.

That streaming design is a good general lesson. The raster contract puts the heavy lifting on the host so the printer can be dumb, and a printer that stays dumb has to process a page as a stream, not a file. Real printers do the same thing with a bigger buffer.

Label printers mostly do not want raster

Here is where the driverless story stops being the whole story. Walk into a warehouse and the printers making shipping labels are commonly thermal printers that speak a command language: ZPL on Zebra and Zebra-compatible devices, EPL on older ones, ESC/POS on receipt printers. The application does not send a bitmap. It sends text like ^FO50,50^BCN,160,Y,N,N^FD1Z999AA10123456784^FS, and the printer draws the barcode itself. Our ZPL field guide covers the language; the point here is the shape of the contract.

That contract is the opposite of driverless. The printer is smart and the host is dumb. The host does not rasterize; it passes bytes through a raw queue with no rendering in between. The reasons the industry settled there are practical: a barcode drawn by the printer is exact at the printer's own dot pitch, a 4x6 label in ZPL is a few kilobytes of text rather than a page-sized raster, the same file usually works across printer generations, and printer settings like darkness and label length are part of the language.

Some label printers also speak IPP, and on those you can send a rendered page through the driverless path. But the capabilities that matter for labels (cutter control, darkness, media calibration, printer-side barcodes) live in the command language, not in the raster attributes. This is the tension in Microsoft's driver retirement: the inbox IPP class driver is designed around Mopria-certified printers that accept raster, and a thermal printer that wants raw ZPL over a vendor driver is not that. We wrote about the timeline and what to test in Microsoft is retiring third-party printer drivers. The honest position is that we have not tested every label printer model under Windows Protected Print, so we cannot tell you whether yours will install. Pilot it on one machine before you commit a fleet.

A print API needs two paths, and they are not interchangeable

Once you see that documents and labels are two different contracts, a remote printing service's job splits cleanly.

Document path Raw path
What the application sends PDF or text ZPL, EPL, or ESC/POS bytes
Who rasterizes The host OS (driverless) or a driver The printer
What the host must do Render and hand to the OS print system Pass bytes through unchanged
Options that make sense Paper size, duplex, color, landscape Copies only
What breaks if you cross them Bytes printed as a page of text A PDF's bytes printed as garbage

RocketPrint is a remote printing API and desktop Station for software platforms: your backend POSTs a print job, and a small app beside the printer prints it, with no inbound networking, no drivers for raw label formats, and a durable job record. Station implements both paths and keeps them separate. A pdf_base64 job goes through the OS document path (a Chromium-based renderer on Windows, the OS path on macOS), so a driverless IPP printer works there for free, with the OS doing exactly what it did for Joshi's e-ink panel. A raw_base64 job bypasses rendering entirely: CUPS with the raw option on macOS, a native Winspool helper on Windows. Station does not implement IPP itself; it relies on the OS, which is why a printer the OS can see is a printer Station can register.

Two limits worth stating. Station runs on macOS and Windows; Linux is not yet available for self-service installation, so a Raspberry Pi beside a printer is not something we can offer today, even though it is the natural host for a project like the one in the post. And for raw jobs the API honors only copies, because everything else about a label is inside the bytes. The printers and formats page and the Station docs describe both paths, and how a cloud printing API works covers the part that has nothing to do with IPP: getting the job to the machine behind the firewall in the first place.

What to take from a printer with an output tray

The e-ink printer is a nice hack, and it is also a precise demonstration that "driverless" is a protocol stack you can hold in your head: an mDNS record, a few IPP operations over HTTP, and a raster format streamed row by row. If you have ever been confused about why some printers just appear and others need an installer, that is the difference.

And if you work on the label side, the same demonstration explains your problem from the other direction. Your printers are not dumb raster sinks. They are small interpreters for a language, and the job of everything upstream is to deliver the program to them intact.

END / hand-built-ipp-printer-what-driverless-means

More field notes →