Docs
A FIFO is a named pipe on the web. This page explains how its envelopes work.
Your first FIFO
In four steps, make a FIFO that replies to each Request with its subject.
-
On the home page, type a name of two or more words, such as
my-shop, or keep the random one, and open the FIFO. Its editor opens. Bookmark the page: anyone who knows the name can change the FIFO. -
Save an ingress envelope with one header line. It accepts every Request that has a
Subject, and saves its text as the capturesubject.Subject: (?<subject>.+)
-
Add an egress envelope to it.
To: -is the answer to the client, and the variable$subjectis the capture.To: - You wrote: $subject -
Send a Request. Click Try
in the editor and type a subject, or use curl.
The editor shows each Request while it is open.
curl -H 'Subject: Hello' -d '' https://mkfifo.com/NAME # The answer: You wrote: Hello
Next: write a URL in To
to send the data to a chat or an API, seal your keys, or start from a template.
A FIFO
-
The FIFO NAME is at
https://mkfifo.com/NAME, and its inbox isNAME@mkfifo.com. - A name has 1 to 64 characters: a to z, 0 to 9, "-", "_" and ".". It cannot be "." or "..".
-
A new FIFO needs a name of two or more words joined with a "-", for example
my-shop. One-word names, of 3 characters or more, are for subscribers. A FIFO that already has envelopes keeps its name. - A FIFO is made of ingress envelopes, and each ingress envelope has egress envelopes.
- Anyone who knows the name can change the envelopes, so seal your secrets (see Sealed fields).
Ingress envelopes
An ingress envelope says which Requests to accept. It looks like an email: header lines, an empty line, and body lines.
From: tickets@theater\.example
Subject: Your tickets for (?<movie>.+)
Confirmation code: (?<code>[A-Z0-9]+)
- Each header line is a pattern for the header field with that name. Header names are not case-sensitive.
- Each body line is a pattern. It looks for a match anywhere in the body, so the order of the lines does not matter.
- A Request must match all the lines. With no body lines, any body matches.
-
(?<name>...)saves a part of the Request as a capture, for the egress envelopes to use. -
The patterns are Perl-compatible.
(?m)makes^and$match at each line, and(?s)makes.match a line break.
Egress envelopes
An egress envelope says what to send, and where. It belongs to one ingress envelope.
In-Reply-To: <ID@mkfifo.com>
To: https://api.mail.example/send
Authorization: <seal:Bearer KEY>
Content-Type: application/json
{"to": "me@example.com", "subject": "Ticket: $movie",
"text": "Movie: $movie\nCode: $code"}
| To | The message |
|---|---|
| https://example.com/hook | An HTTP request with the header lines and the body. The method is POST, or the value of X-HTTP-Method-Override. |
| my-budget | A Request to the FIFO my-budget. The FIFOs cannot make a loop. |
| - (or reply:) | The answer to the client: a page, or a redirect. It can have Status, Content-Type, Location and Cache-Control. A Location of a different site works only for a POST from a form of that site, so no FIFO is a short link to a different site. With Cache-Control: public, Cloudflare caches the answer, and a GET during that time does not reach the FIFO. A 200 answer with no cookie has an ETag: a GET with the same tag in If-None-Match gets 304. With Content-Type: text/markdown, the answer is a page in the frame of the site: Subject is its title, and Date, Last-Modified and Keywords show under it. |
| Reply-To: - or Reply-To: my-orders | Where the reply of an HTTP request or of a FIFO goes: to the client, or as a Request to the FIFO my-orders, with the Status, Content-Type and body of the reply. |
| Errors-To: my-alerts | When the egress fails, a Request to the FIFO my-alerts, with Error (the cause) and Envelope (the Message-ID of the egress envelope). |
| me@example.com | An email: from a FIFO in your home to your own address, or from the FIFOs of mkfifo.com. To email anyone else, use the API of your email provider. |
-
$nameputs the text of a capture there. In a JSON body, the text is escaped for JSON; in an HTML body, for HTML. -
A builtin runs inside
$( ), like a shell command:$(date -I)or$(random 8). Its words are its arguments, and its output goes in its place. The builtins have the same names and the usual options as in a shell: atom, base32768, base64, basenc, cat, cut, date, echo, eme, eval, gpasswd, grep, groups, gunzip, gzip, head, hmac, jq, ls, lsd, lsf, lsjson, lsl, magick, markdown, md5sum, mkpasswd, newgrp, obscure, printf, random, resvg, reveal, scrypt, secretbox, sed, sh, sha256sum, sort, tail, tee, totp, tr, uniq, uuidgen, wc, xargs, zstd. No builtin can reach the network or write a file. -
A pipe passes a value to a builtin or to a FIFO, as in a shell:
$email | tr A-Z a-z | md5sum. A FIFO gets the value as the body of a Request, and its answer goes in its place:$($name | lower)with your FIFO lower. Only a builtin can start$( ). -
A redirection reads or writes a FIFO, the way a shell reads or writes a file:
$(wc -l < prices)reads the answer to a GET of prices,$(printf %s $id > audit)writes into audit, and$($body | jq -r .id 2>/dev/null)is empty when jq fails.2>&1gives the error as the value instead. -
$(totp $email)gives a one-time code. In a pattern,Code: ^$(totp $email)$matches a valid code, once. -
In an HTML body,
$($name | markdown)renders the capture as Markdown: headings, lists, tables, code, bold, italics and https links. - An ingress envelope with two or more egress envelopes works like tee: each one sends.
-
mkfifo.com does not retry a failed egress envelope. With
Errors-To, the error goes to a FIFO. PutErrors-Toin the ingress envelope for all its egress envelopes, or in one egress envelope.
Requests
-
POST https://mkfifo.com/NAMEis a Request, with the header fields and the body of the HTTP request. -
X-HTTP-Method-Overrideis the method: the header field from the client, or else the query parameter_method, or else POST. -
A form gives one line
name: valuefor each field. Put a field that has more than one line last. -
An email to
NAME@mkfifo.comis a Request too: its decoded header fields, and its plain text, or its HTML when it has no plain text. An envelope withContent-Type: text/htmlreads the HTML of the email, also when the email has plain text. -
X-MkFIFO-DMARCis the DMARC result of an email. A sender can fake the From line, soX-MkFIFO-DMARC: ^pass$together with a pattern on From proves who sent it. -
X-MkFIFO-Form-Secondsis on a message from the form of a page only: the seconds from the page load to the send. A program is faster than a person, soX-MkFIFO-Form-Seconds: ^(|[5-9]|[1-9][0-9]+)$refuses a form that is sent in less than 5 seconds.
Answers
The answer to POST /NAME
tells you what happened. Send a test Request with curl, and read it.
- 200
- The Request matched an ingress envelope, and all its egress envelopes were sent.
- 204
- The Request matched no ingress envelope, or the FIFO has none yet, so mkfifo.com dropped it. An email is not sent back.
- 404
- The name is not valid: no FIFO can have it.
- 413
- The Request is too large.
- 429
- Too many Requests from your address. Retry-After says when to try again.
- 502
- The Request matched, but an egress envelope failed.
- 503
- Too many Requests from your address at the same time, or too many large bodies. Retry-After says when to try again.
With 200 and 502, the answer has one line for each egress envelope: its Message-ID, then
ok
or failed. An answer egress (To: -) gives its own answer.
Selector lines
A header line that starts with $
finds values in the body. Its pattern then checks them, like the value of a header field:
$.id: ^(?<order>\d+)$
$.items[*].sku: (?<skus>.+)
$.coupon: ^$
$("td.code"): ^(?<code>[A-Z0-9]+)$
$("a.ticket").attr("href"): ^(?<link>https://.+)$
-
$.name,$["name"],$[0],$.items[*]and$..namefind values in a JSON body. A string gives its text; a number, an object or an array gives its JSON. -
$("CSS selector")finds elements in an HTML body. Each element gives its text.$("a.ticket").attr("href")gives an attribute of each element instead: here, its link. -
Most emails have plain text and HTML. Add
Content-Type: text/htmlto the envelope, and its CSS selectors read the HTML. -
Several values are joined with a comma and a space. With no value, the pattern checks an empty value:
^$accepts only a body with no coupon.
Fields of a form or JSON
When Content-Type
matches a form or JSON, each body line is a field of the body: a name, a colon, and a pattern for its value.
Content-Type: form
name: (?<name>\S.*)
email: ^(?<email>[^\s@]+@[^\s@]+)$
website: ^$
-
Content-Type: formmatches both types of HTML form.Content-Type: application/jsonmatches JSON. -
For JSON, a name is a key of the top-level object, or a JSON path:
$.order.id: ^\d+$. - A missing field counts as an empty value. "Try" sends a real form, just like an HTML form. To try a FIFO with no real egress, turn that egress envelope off ("Turn off") and add an egress to a FIFO that logs the Request.
Schedules
An ingress envelope in a FIFO of your home can run on a schedule. Write two lines, and mkfifo.com sends it a Request at each time:
From: crontab\nDate: 0 9 * * MON-FRI
-
Dateis a cron pattern: minute, hour, day of the month, month and day of the week. The time is UTC. For a different time zone, writeCRON_TZ=Europe/Paris 0 9 * * *. -
For one time only, write
From: atandDate: 2026-10-25T09:00Z. -
The Request is a POST with no body. Its
Dateis the time of the run. Then the egress envelopes run, as for each Request. - At most 10 schedules for each account, and at most one run in 15 minutes. A schedule that is off does not run.
Hold a different FIFO
An ingress envelope whose To is a different, free FIFO holds that FIFO: the Requests to that FIFO come to the envelope. A GET of a held FIFO is a Request too, with the query as the fields of a form, but only for an envelope with an X-HTTP-Method-Override line. So a public form at
/my-hello
can keep its envelopes under a secret name:
To: my-hello X-HTTP-Method-Override: ^GET$
In-Reply-To: <ID@mkfifo.com>
To: -
Content-Type: text/html; charset=utf-8
<form method="post">
<label>Email <input name="email" type="email" required></label>
<label>Message <textarea name="message" required></textarea></label>
<button>Send</button>
</form>
A page in an answer cannot run scripts, and its forms can send only to mkfifo.com.
A new held FIFO needs a name of two or more words, with 6 or more characters. A hold is not a lock: anyone who knows the name of the holder can change its envelopes, so give the holder a random name of 26 or more characters that nobody can guess.
When you sign in, you get a home that only you can see and change: /~. Each path under it is a FIFO: open
/~/work
and save envelopes there. The FIFOs in your home are never public; only they can send Requests to each other, with a path: To: ~/inbox. The line
Bcc: my-shop
in an envelope links it to the public FIFO my-shop, like a symbolic link: the envelope gets the Requests to my-shop, and the editor of
my-shop
does not show it. The link gets each Request after the envelopes of my-shop, and never answers its client. A link only points to the FIFO: it changes nothing there, and a free name stays free.
The To of a holder can also have a route: a path after the name, in the URLPattern syntax of the web. Each group is a capture. With this envelope,
GET /my-shop/apple
is a Request to my-shop, and $item is apple:
To: my-shop/:item X-HTTP-Method-Override: ^GET$
-
:name(PATTERN)gives a group its own pattern, and ?, * or + after a group makes it optional or repeated::path+takes one or more segments. -
An envelope with no route takes only
/my-shop. Emails and messages from a form have no path. - /images/width=192/favicon.svg gives the mkfifo.com icon as a 192-pixel PNG, from a route.
-
/images/OPTIONS/NAMEgives the image of the FIFO NAME, with the options width, height and format, for examplewidth=192,format=webp: the builtin magick converts the answer to a GET of the FIFO. - /fpo/300x250?text=Hero+image gives a 300 by 250 pixel placeholder SVG, like fpoimg.com: the query fields text, bg_color, text_color and dims=false change it.
Mirrors
A line Cc
or Bcc
in an ingress envelope gives it the Requests of more FIFOs, as in an email. In the editor of each of these FIFOs, the envelope shows as a mirror: you can read it there, but you edit it only in its home.
Delivered-To
tells you which FIFO got the Request:
Cc: my-support From: (?<from>.+@.+) Subject: (?<subject>.+) Delivered-To: ^(?<fifo>[a-z0-9._-]+)@
-
A mirror does not show the name of its home, its
Bcclines, or its destinations: a name is the key to change a FIFO. - The editor of a FIFO can remove any of its mirrors.
- A mirror cannot go into a name of mkfifo.com, or into a FIFO that a different FIFO holds. A new FIFO with a mirror in it needs a name of two or more words, with 6 or more characters.
- The FIFO support shows the envelope of contact as a mirror.
Sealed fields
Write <seal:VALUE>
as the value of a header field, or close the lock of the field in the editor. mkfifo.com encrypts the value for this FIFO, and shows only <sealed:TOKEN>. Use it for tokens, keys, and URLs with secrets in them.
Send email
-
A FIFO in your home can email you: write the address that you sign in with in To, for example
To: you@example.com. You get at most 10 of these emails an hour. A link in your home can forward each message of a public contact form to you this way. - To email anyone else, use your own email provider: write the URL of its API in To, your key in a sealed field, and the message in the body, as in the example above. The email can then go to any address that your provider accepts, also to the address of a capture.
- The FIFOs of mkfifo.com, for example contact, send email from noreply@mkfifo.com.
Use a FIFO with curl
Each envelope is plain text, so a script can save it. The answer to a save is the saved envelope. Its line
Message-ID: <ID@mkfifo.com>
gives the ID for the commands below: an egress envelope points to it with In-Reply-To, a save with this
Message-ID
line changes it, and a DELETE
with it deletes it.
# Save an ingress envelope. The answer is the saved envelope, with its
# line Message-ID: <ID@mkfifo.com>.
printf 'X-GitHub-Event: ^push$\n\n"full_name":\\s*"(?<repo>[^"]+)"\n' |
curl -X PUT --data-binary @- https://mkfifo.com/my-deploys
# Save an egress envelope that replies to it: its In-Reply-To is the ID.
printf 'In-Reply-To: <ID@mkfifo.com>\nTo: https://chat.example/hooks/T000\n\n{"text": "New push to $repo"}\n' |
curl -X PUT --data-binary @- https://mkfifo.com/my-deploys
# Change the ingress envelope: send it again, with its Message-ID.
printf 'Message-ID: <ID@mkfifo.com>\nX-GitHub-Event: ^(push|release)$\n\n"full_name":\\s*"(?<repo>[^"]+)"\n' |
curl -X PUT --data-binary @- https://mkfifo.com/my-deploys
# Send a test Request, and read the answer.
curl -H 'X-GitHub-Event: push' -d '{"full_name": "a/b"}' https://mkfifo.com/my-deploys
# Delete an envelope.
curl -X DELETE -H 'Message-ID: <ID@mkfifo.com>' https://mkfifo.com/my-deploys
The builtins as web APIs
Each builtin is also a virtual FIFO, with two slashes in its address: https://mkfifo.com//bin/sh, //bin/jq,
and the other builtins. The two slashes are on purpose: the name of a virtual FIFO starts with a slash, so nobody else can use it.
//dev/null
drops every Request. An envelope can send to them too: $body | /bin/sort, or
To: /bin/sh
with a script in the body.
# Each builtin is a web API: the body is the input, and the query gives the words.
printf 'b\na\n' | curl --data-binary @- https://mkfifo.com//bin/sort
curl --data-binary '{"items": [1, 2]}' 'https://mkfifo.com//bin/jq?-c&.items'
# A script of the shell of mkfifo.com.
curl --data-binary 'echo hello | tr a-z A-Z' https://mkfifo.com//bin/sh
The terminal
The terminal runs the builtins with the same syntax as an envelope, so you can try a command before you put it in an envelope.
//bin/sh?form
opens it. In an editor, Ctrl+` opens and closes it; on a touch screen, tap the name of the FIFO three times quickly.
help
shows the builtins.
-
Each line is a Request to
/bin/sh:printf '%s\n' b a | sort. -
A variable stays for the next lines, like a capture in an envelope:
body='{"id": 7}', then$body | jq .id. -
$body | NAMEsends a Request to the FIFO NAME and shows its answer. When you are signed in,~and~/PATHare your home and the FIFOs in it, andls ~lists them. - The page keeps your lines and variables until you close it. mkfifo.com stores none of them.
Connect an assistant
https://mkfifo.com/mcp
is an MCP endpoint: an assistant that speaks the Model Context Protocol, such as Claude Code, connects to it with no account and no key.
It has one tool, run, which runs a script of the same shell as the terminal and gives its output.
So your assistant can use the builtins, and send Requests to your FIFOs with a pipe.
# Connect Claude Code to mkfifo.com.
claude mcp add --transport http mkfifo https://mkfifo.com/mcp
# The same tool with curl: a JSON-RPC message for each request.
curl https://mkfifo.com/mcp -H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": "run", "arguments": {"script": "echo hello | tr a-z A-Z"}}}'
- The endpoint is a FIFO of the site. Its envelopes are its rules, and its editor shows them: one ingress envelope for each method of the protocol, and for each error.
-
A script has no access to your home, as a Request from
curl. mkfifo.com keeps nothing of the scripts.
Security model
How mkfifo.com protects your FIFOs and your data, and what is up to you.
- A public FIFO is open. Anyone who knows its name can read and change its envelopes. Pick a name that is hard to guess, put keys in sealed fields, and keep private work in your home.
-
An email address is a name too.
When you give
NAME@mkfifo.comto a shop, the shop knows the name of the FIFO, so it can change the FIFO. For an address that you give out, use a long random name, and put the envelopes that send your data on in your home, with a link to that name. - Sealed fields stay secret. mkfifo.com encrypts a sealed value for one FIFO. The editor, copies and templates show only a token, and the value works only in that FIFO.
- Your home is private. Only you can see and change the FIFOs in your home. They get Requests only from your other FIFOs and from the public FIFOs that you link to. A link only points to a public FIFO: it changes nothing there, and the people who edit that FIFO cannot see it.
- Sign-in with no password. You get a link by email. It works once, for at most 15 minutes. You never type a code: only the link has it, so a different page cannot ask you for it. After 5 wrong codes from one internet address, that address waits 15 minutes; after 50 wrong codes for your email address in all, every sign-in for it waits 15 minutes. Your sign-in is a signed cookie for 60 days, and Sign out removes it.
- Requests are not stored. mkfifo.com does not keep the Requests to your FIFOs. The editor shows them only while it is open.
- The cookies of mkfifo.com stay with mkfifo.com. No Request to a FIFO gets them, and only the FIFOs of the site can set a cookie.
- A FIFO cannot act as you. Each FIFO is a path of mkfifo.com, so the browser takes its answer as a part of the site. Thus the answer of a FIFO runs no script, and a page of mkfifo.com runs only the scripts of mkfifo.com, never a FIFO. No FIFO can read your pages, send forms as you, or stay in your browser.
- No short links to other sites. A link to mkfifo.com never takes you to a different site. The answer of a FIFO can send you on to another site only when you came from a form on that site, as a thank-you page after a contact form. So a phishing email cannot hide its link behind the name of mkfifo.com.
- No spam from mkfifo.com. mkfifo.com sends email only from its own FIFOs, such as the sign-in, and from a FIFO in your home to your own address. Nobody can use it to email other people.
- No endless loops. A save that makes FIFOs send to each other in a circle fails. A Request that comes back to a FIFO through a link stops there. A Request goes through at most 8 FIFOs, and sends at most 1,000 messages.
- Fair use. Each internet address has rate limits: see Limits.
- Found a problem? Write to security@mkfifo.com or use the security form. See also security.txt.
Limits
- Size of an envelope
- 32 KiB
- Ingress envelopes in a FIFO
- No limit: 1,000 steps for each one that examines a Request, and the steps of its matches
- Egress envelopes for one ingress envelope
- 4
- Body of a Request
- 1 MiB
- Size of an email
- 25 MiB
- Time for one HTTP egress
- 10 seconds
- Hops from FIFO to FIFO
- 8
- Depth of JSON for a selector line
- 64
- Egress messages for one Request
- 1,000
- Requests to one FIFO from one address
- 120 in 1 minute
- Requests of one address at the same time
- 4, then a wait of at most 20 seconds
- Messages from forms, from one address
- 60 in 10 minutes, 20 to mkfifo.com
- Saves and deletes from one address
- 60 in 10 minutes
- Copies from one address
- 10 in 1 hour
- Redirects that a download follows
- 5