smolboid/daemon/locales/en-US/main.ftl

201 lines
8.4 KiB
Text
Raw Permalink Normal View History

docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
## Every string the daemon mails to an author. Operator CLI output is not
## here, deliberately: see the i18n module header.
##
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
## House style, in short. Write to "you" and use contractions. Sentence case.
## Start with a verb where you can. Say what happened and what to do next,
## without blaming anyone and without the words invalid, illegal or fatal.
## Use the words the clients use: "header", not frontmatter field; "Requests
## folder", not requests tier; "post", not record, twt or entry. No em dashes.
##
## These are read in plain-text mail clients and on phones, so keep lines
## short and leave markup out.
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
## Replies about a message we couldn't publish
reply-subject = twtxt: { $reason }
reply-body =
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
{ $reason }
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
Here's the message:
{ $quoted }
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
## Reasons. Each one is a whole sentence: it becomes the subject line, and
## the body opens with it.
##
## Each MUST be a single line, however long. A reason goes into a mail header,
## and a line break there breaks the whole header block, so the reply arrives
## with no subject at all. Fluent keeps the newlines in a wrapped value, so
## there's no safe way to wrap these. `every_reason_is_a_single_line` checks
## it. When the fix needs more room, put it in a detail paragraph below.
reason-body-not-utf8 = We couldn't read the text in this message. Try sending it again.
reason-post-not-canonical = We couldn't rebuild this message exactly as you sent it, so we can't prove it's yours. Send the text on its own.
reason-repost-not-canonical = We couldn't rebuild this repost exactly as you sent it. Send it again with only the Twt-Repost header.
reason-fields-beyond-reply-to = This post carries extra headers. Send it again with just the text.
reason-nick-invalid = A nick can use a-z, 0-9 and hyphens, up to 30 characters.
reason-delete-needs-timestamp = Twt-Delete needs the time of the post to remove, like 2026-10-14T19:39:39Z.
reason-prune-mode-unknown = Twt-Prune takes fifo or off, not "{ $got }".
## Limits on what a post can contain
reason-too-long = A post can be up to { $max } characters. Shorten it and send it again.
reason-forbidden-char = A post has to fit on one line, without tabs or other control characters. Take them out and send it again.
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
reason-bad-timestamp = That isn't a time we can read. Use the form 2026-10-14T19:39:39Z.
reason-duplicate-timestamp = Your feed already has a post at that time.
## Quota and retention
reason-over-quota-post = You've reached today's limit for posts. Try again tomorrow.
reason-over-quota-repost = You've reached today's limit for reposts. Try again tomorrow.
reason-collision = Your feed already has a post at { $timestamp }. Wait a second, then send it again.
reason-feed-full = Your feed is full. Remove a post with Twt-Delete, or send Twt-Prune: fifo to drop the oldest ones from now on.
reason-reply-to-changed = This feed belongs to { $author }. Send your posts from that address.
reason-no-record-at = Your feed has no post at { $timestamp }.
## Commands
reason-one-command-per-mail = Send one command per message. Twt-Register, Twt-Help, Twt-Admit, Twt-Block and Twt-Registration each need their own, and feed commands need another.
reason-bad-address = "{ $got }" isn't a smolmail address.
reason-registration-mode-unknown = Twt-Registration takes open, closed or silent, not "{ $got }".
## An accept token in a post. The author didn't do anything wrong: their
## client added the token because they accepted this address. The fix depends
## on which client they use, so it goes in a detail paragraph.
reason-accept-field-on-post = A post can't carry the accept token your client attached.
reason-accept-field-on-first-post = Your first post carried an accept token, which we can't publish.
## Detail paragraphs, added to the end of the reply when one line isn't
## enough room for the fix. Most reasons don't need one.
reason-accept-field-on-post-detail =
We publish your post by rebuilding exactly what you signed, so anyone can
check it was you. A token can't be part of that.
We've got your token now, so send the same post again and it should go up.
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
If it's refused again, your client is adding the token to every message
instead of sending it once. Block this address in your client and send
the post again. Blocking only moves our replies back to your Requests
folder.
reason-accept-field-on-first-post-detail =
Your feed is fine. It's there and it's waiting for its first post.
We publish your post by rebuilding exactly what you signed, so anyone can
check it was you. A token can't be part of that.
We've got your token now, so send that post again and it should go up.
If it's refused again, your client is adding the token to every message
instead of sending it once. Block this address in your client and send
the post again. Blocking only moves our replies back to your Requests
folder.
## Warning as a feed fills up
cap-warning-subject = twtxt: your feed is nearly full
cap-warning-body =
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
Your feed has reached 90% of its { $cap } limit.
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
When it's full, new posts are refused. Remove some with Twt-Delete, or
send Twt-Prune: fifo and we'll drop the oldest ones to make room.
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
## The mail that hands over the accept token
welcome-subject = twtxt: your feed is ready
welcome-body =
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
Your feed is live: { $url }
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
Send a message to this address and it becomes a post.
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
To manage your feed, add one of these headers: Twt-Nick, Twt-Delete,
Twt-Repost or Twt-Prune. Send Twt-Help for what each one does.
## First contact from someone who doesn't have a feed
feat: answer unknown senders, gated on proving they hold their address process now splits by whether the sender owns a feed. The owner path runs first, so a rotated owner is never mistaken for a registration, and behaves exactly as before for ordinary mail. The stranger path is the new one, and every branch of it is gated. No signed Reply-To means no address to verify or answer, so it stays a silent drop. The blocked check runs before anything touches the network, so Twt-Block cannot be sidestepped by re-registering. Silent mode stays silent, which is how every version before this behaved — except for an admin, who would otherwise have no way to turn registration back on. Then the gate the whole change rests on: before any outbound mail, the claimed address is resolved on its own server and the resolved key must equal the key that signed the request. Without it a signed Reply-To naming someone else's address would be enough to take their feed, and replying to the claimed smol:// URI would pin the sender's key as verified under that address — a permanent denial of service against whoever really owns it. Every reply is queued against the short form for the same reason; nothing hands a self-certifying URI to send. Registration attempts are counted before the dial rather than after, since one that fails its key check costs the same connection. A per-cycle dial budget bounds how long the state lock is held, and a mail over budget keeps its row instead of being dropped. Admin commands are recognised only from an admin address, and only Admit, Block and Registration count as admin commands — an admin's Twt-Help means Twt-Help. A non-admin sending one gets the ordinary answer and is never told admin commands exist. Rig still passes, so the owner path is unchanged in practice as well as in intent.
2026-10-14 20:21:11 +03:00
info-subject = twtxt: how to get a feed
info-body =
You can publish a twtxt feed here by sending mail.
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
To get one, send a message to this address with this header:
feat: answer unknown senders, gated on proving they hold their address process now splits by whether the sender owns a feed. The owner path runs first, so a rotated owner is never mistaken for a registration, and behaves exactly as before for ordinary mail. The stranger path is the new one, and every branch of it is gated. No signed Reply-To means no address to verify or answer, so it stays a silent drop. The blocked check runs before anything touches the network, so Twt-Block cannot be sidestepped by re-registering. Silent mode stays silent, which is how every version before this behaved — except for an admin, who would otherwise have no way to turn registration back on. Then the gate the whole change rests on: before any outbound mail, the claimed address is resolved on its own server and the resolved key must equal the key that signed the request. Without it a signed Reply-To naming someone else's address would be enough to take their feed, and replying to the claimed smol:// URI would pin the sender's key as verified under that address — a permanent denial of service against whoever really owns it. Every reply is queued against the short form for the same reason; nothing hands a self-certifying URI to send. Registration attempts are counted before the dial rather than after, since one that fails its key check costs the same connection. A per-cycle dial budget bounds how long the state lock is held, and a mail over budget keeps its row instead of being dropped. Admin commands are recognised only from an admin address, and only Admit, Block and Registration count as admin commands — an admin's Twt-Help means Twt-Help. A non-admin sending one gets the ordinary answer and is never told admin commands exist. Rig still passes, so the owner path is unchanged in practice as well as in intent.
2026-10-14 20:21:11 +03:00
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
Twt-Register: yournick
test: cover self-service registration, and fix what it found Rig legs for the whole author path with no CLI admit anywhere: an unknown sender drawing the info mail, Twt-Register creating the feed, the token arriving so she can publish, Twt-Help, the admin notification, Twt-Block stopping a feed and Twt-Registration closing the door. Pentest probes for the attacks: no Reply-To, a claim on someone else's address, a claim on an address nobody holds, an admin command from a non-admin, and a blocked author coming back. Two real bugs, both found by asserting on what the author receives rather than on what the daemon meant to send. Five catalogue entries were wrapped across lines, and a reason is interpolated into the reply's Subject. A frontmatter value carrying a line break makes the whole block malformed, so smolmail fails closed toward display (SPEC.md sec 5.5) and the reply arrives with no subject at all — every rejection, silently. The same bug class the parent commit fixed sender-side; step 2 reintroduced it from the catalogue, because the Rust prose it replaced used backslash continuations that join without a newline. Reasons are single-line now, with a test over the enum that fails if one ever wraps again. And the flow had a step that could not work. Telling an author to accept this mailbox makes their client attach an Accept token to every later mail, and a published record is verified by rebuilding the exact bytes that were signed — which cannot include a token without publishing it. So every post from an author who followed that advice was refused forever. The info mail now says not to accept the address and where to find replies instead, and that refusal has its own reason, since "resend the bare text" is useless advice when the client adds the field by itself. Three of my own probes were wrong too, kept here as comments so they are not rewritten the same way: grep -q under `set -o pipefail` SIGPIPEs its producer and can report failure for a subject that is plainly present; the refusal reply lands in the main tier precisely because accepting the mailbox is what moves replies there; and blocking an address that was never accepted is a store no-op, so the probe has to register first.
2026-10-14 20:51:21 +03:00
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
Headers sit next to the subject in most clients, often behind a button
marked "+ header". Leave the value empty and we'll use your username.
test: cover self-service registration, and fix what it found Rig legs for the whole author path with no CLI admit anywhere: an unknown sender drawing the info mail, Twt-Register creating the feed, the token arriving so she can publish, Twt-Help, the admin notification, Twt-Block stopping a feed and Twt-Registration closing the door. Pentest probes for the attacks: no Reply-To, a claim on someone else's address, a claim on an address nobody holds, an admin command from a non-admin, and a blocked author coming back. Two real bugs, both found by asserting on what the author receives rather than on what the daemon meant to send. Five catalogue entries were wrapped across lines, and a reason is interpolated into the reply's Subject. A frontmatter value carrying a line break makes the whole block malformed, so smolmail fails closed toward display (SPEC.md sec 5.5) and the reply arrives with no subject at all — every rejection, silently. The same bug class the parent commit fixed sender-side; step 2 reintroduced it from the catalogue, because the Rust prose it replaced used backslash continuations that join without a newline. Reasons are single-line now, with a test over the enum that fails if one ever wraps again. And the flow had a step that could not work. Telling an author to accept this mailbox makes their client attach an Accept token to every later mail, and a published record is verified by rebuilding the exact bytes that were signed — which cannot include a token without publishing it. So every post from an author who followed that advice was refused forever. The info mail now says not to accept the address and where to find replies instead, and that refusal has its own reason, since "resend the bare text" is useless advice when the client adds the field by itself. Three of my own probes were wrong too, kept here as comments so they are not rewritten the same way: grep -q under `set -o pipefail` SIGPIPEs its producer and can report failure for a subject that is plainly present; the refusal reply lands in the main tier precisely because accepting the mailbox is what moves replies there; and blocking an address that was never accepted is a store no-op, so the probe has to register first.
2026-10-14 20:51:21 +03:00
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
Our reply arrives in your Requests folder, not your inbox, because you
haven't accepted this address. We recommend leaving it that way.
test: cover self-service registration, and fix what it found Rig legs for the whole author path with no CLI admit anywhere: an unknown sender drawing the info mail, Twt-Register creating the feed, the token arriving so she can publish, Twt-Help, the admin notification, Twt-Block stopping a feed and Twt-Registration closing the door. Pentest probes for the attacks: no Reply-To, a claim on someone else's address, a claim on an address nobody holds, an admin command from a non-admin, and a blocked author coming back. Two real bugs, both found by asserting on what the author receives rather than on what the daemon meant to send. Five catalogue entries were wrapped across lines, and a reason is interpolated into the reply's Subject. A frontmatter value carrying a line break makes the whole block malformed, so smolmail fails closed toward display (SPEC.md sec 5.5) and the reply arrives with no subject at all — every rejection, silently. The same bug class the parent commit fixed sender-side; step 2 reintroduced it from the catalogue, because the Rust prose it replaced used backslash continuations that join without a newline. Reasons are single-line now, with a test over the enum that fails if one ever wraps again. And the flow had a step that could not work. Telling an author to accept this mailbox makes their client attach an Accept token to every later mail, and a published record is verified by rebuilding the exact bytes that were signed — which cannot include a token without publishing it. So every post from an author who followed that advice was refused forever. The info mail now says not to accept the address and where to find replies instead, and that refusal has its own reason, since "resend the bare text" is useless advice when the client adds the field by itself. Three of my own probes were wrong too, kept here as comments so they are not rewritten the same way: grep -q under `set -o pipefail` SIGPIPEs its producer and can report failure for a subject that is plainly present; the refusal reply lands in the main tier precisely because accepting the mailbox is what moves replies there; and blocking an address that was never accepted is a store no-op, so the probe has to register first.
2026-10-14 20:51:21 +03:00
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
We publish nothing until you ask, and this is the only mail you'll get
until you do.
feat: answer unknown senders, gated on proving they hold their address process now splits by whether the sender owns a feed. The owner path runs first, so a rotated owner is never mistaken for a registration, and behaves exactly as before for ordinary mail. The stranger path is the new one, and every branch of it is gated. No signed Reply-To means no address to verify or answer, so it stays a silent drop. The blocked check runs before anything touches the network, so Twt-Block cannot be sidestepped by re-registering. Silent mode stays silent, which is how every version before this behaved — except for an admin, who would otherwise have no way to turn registration back on. Then the gate the whole change rests on: before any outbound mail, the claimed address is resolved on its own server and the resolved key must equal the key that signed the request. Without it a signed Reply-To naming someone else's address would be enough to take their feed, and replying to the claimed smol:// URI would pin the sender's key as verified under that address — a permanent denial of service against whoever really owns it. Every reply is queued against the short form for the same reason; nothing hands a self-certifying URI to send. Registration attempts are counted before the dial rather than after, since one that fails its key check costs the same connection. A per-cycle dial budget bounds how long the state lock is held, and a mail over budget keeps its row instead of being dropped. Admin commands are recognised only from an admin address, and only Admit, Block and Registration count as admin commands — an admin's Twt-Help means Twt-Help. A non-admin sending one gets the ordinary answer and is never told admin commands exist. Rig still passes, so the owner path is unchanged in practice as well as in intent.
2026-10-14 20:21:11 +03:00
## First contact while registration is closed
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
info-closed-subject = twtxt: we're not taking new feeds
feat: answer unknown senders, gated on proving they hold their address process now splits by whether the sender owns a feed. The owner path runs first, so a rotated owner is never mistaken for a registration, and behaves exactly as before for ordinary mail. The stranger path is the new one, and every branch of it is gated. No signed Reply-To means no address to verify or answer, so it stays a silent drop. The blocked check runs before anything touches the network, so Twt-Block cannot be sidestepped by re-registering. Silent mode stays silent, which is how every version before this behaved — except for an admin, who would otherwise have no way to turn registration back on. Then the gate the whole change rests on: before any outbound mail, the claimed address is resolved on its own server and the resolved key must equal the key that signed the request. Without it a signed Reply-To naming someone else's address would be enough to take their feed, and replying to the claimed smol:// URI would pin the sender's key as verified under that address — a permanent denial of service against whoever really owns it. Every reply is queued against the short form for the same reason; nothing hands a self-certifying URI to send. Registration attempts are counted before the dial rather than after, since one that fails its key check costs the same connection. A per-cycle dial budget bounds how long the state lock is held, and a mail over budget keeps its row instead of being dropped. Admin commands are recognised only from an admin address, and only Admit, Block and Registration count as admin commands — an admin's Twt-Help means Twt-Help. A non-admin sending one gets the ordinary answer and is never told admin commands exist. Rig still passes, so the owner path is unchanged in practice as well as in intent.
2026-10-14 20:21:11 +03:00
info-closed-body =
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
Thanks for writing. We're not taking new feeds right now, so there's
nothing to do here yet.
feat: answer unknown senders, gated on proving they hold their address process now splits by whether the sender owns a feed. The owner path runs first, so a rotated owner is never mistaken for a registration, and behaves exactly as before for ordinary mail. The stranger path is the new one, and every branch of it is gated. No signed Reply-To means no address to verify or answer, so it stays a silent drop. The blocked check runs before anything touches the network, so Twt-Block cannot be sidestepped by re-registering. Silent mode stays silent, which is how every version before this behaved — except for an admin, who would otherwise have no way to turn registration back on. Then the gate the whole change rests on: before any outbound mail, the claimed address is resolved on its own server and the resolved key must equal the key that signed the request. Without it a signed Reply-To naming someone else's address would be enough to take their feed, and replying to the claimed smol:// URI would pin the sender's key as verified under that address — a permanent denial of service against whoever really owns it. Every reply is queued against the short form for the same reason; nothing hands a self-certifying URI to send. Registration attempts are counted before the dial rather than after, since one that fails its key check costs the same connection. A per-cycle dial budget bounds how long the state lock is held, and a mail over budget keeps its row instead of being dropped. Admin commands are recognised only from an admin address, and only Admit, Block and Registration count as admin commands — an admin's Twt-Help means Twt-Help. A non-admin sending one gets the ordinary answer and is never told admin commands exist. Rig still passes, so the owner path is unchanged in practice as well as in intent.
2026-10-14 20:21:11 +03:00
Nothing was published and nothing was kept.
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
## Twt-Help
feat: answer unknown senders, gated on proving they hold their address process now splits by whether the sender owns a feed. The owner path runs first, so a rotated owner is never mistaken for a registration, and behaves exactly as before for ordinary mail. The stranger path is the new one, and every branch of it is gated. No signed Reply-To means no address to verify or answer, so it stays a silent drop. The blocked check runs before anything touches the network, so Twt-Block cannot be sidestepped by re-registering. Silent mode stays silent, which is how every version before this behaved — except for an admin, who would otherwise have no way to turn registration back on. Then the gate the whole change rests on: before any outbound mail, the claimed address is resolved on its own server and the resolved key must equal the key that signed the request. Without it a signed Reply-To naming someone else's address would be enough to take their feed, and replying to the claimed smol:// URI would pin the sender's key as verified under that address — a permanent denial of service against whoever really owns it. Every reply is queued against the short form for the same reason; nothing hands a self-certifying URI to send. Registration attempts are counted before the dial rather than after, since one that fails its key check costs the same connection. A per-cycle dial budget bounds how long the state lock is held, and a mail over budget keeps its row instead of being dropped. Admin commands are recognised only from an admin address, and only Admit, Block and Registration count as admin commands — an admin's Twt-Help means Twt-Help. A non-admin sending one gets the ordinary answer and is never told admin commands exist. Rig still passes, so the owner path is unchanged in practice as well as in intent.
2026-10-14 20:21:11 +03:00
help-subject = twtxt: your commands
help-body =
Your feed: { $url }
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
Send a message to this address and it becomes a post. We publish exactly
what you write, on one line.
feat: answer unknown senders, gated on proving they hold their address process now splits by whether the sender owns a feed. The owner path runs first, so a rotated owner is never mistaken for a registration, and behaves exactly as before for ordinary mail. The stranger path is the new one, and every branch of it is gated. No signed Reply-To means no address to verify or answer, so it stays a silent drop. The blocked check runs before anything touches the network, so Twt-Block cannot be sidestepped by re-registering. Silent mode stays silent, which is how every version before this behaved — except for an admin, who would otherwise have no way to turn registration back on. Then the gate the whole change rests on: before any outbound mail, the claimed address is resolved on its own server and the resolved key must equal the key that signed the request. Without it a signed Reply-To naming someone else's address would be enough to take their feed, and replying to the claimed smol:// URI would pin the sender's key as verified under that address — a permanent denial of service against whoever really owns it. Every reply is queued against the short form for the same reason; nothing hands a self-certifying URI to send. Registration attempts are counted before the dial rather than after, since one that fails its key check costs the same connection. A per-cycle dial budget bounds how long the state lock is held, and a mail over budget keeps its row instead of being dropped. Admin commands are recognised only from an admin address, and only Admit, Block and Registration count as admin commands — an admin's Twt-Help means Twt-Help. A non-admin sending one gets the ordinary answer and is never told admin commands exist. Rig still passes, so the owner path is unchanged in practice as well as in intent.
2026-10-14 20:21:11 +03:00
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
To manage your feed, add one of these headers. One per message.
feat: answer unknown senders, gated on proving they hold their address process now splits by whether the sender owns a feed. The owner path runs first, so a rotated owner is never mistaken for a registration, and behaves exactly as before for ordinary mail. The stranger path is the new one, and every branch of it is gated. No signed Reply-To means no address to verify or answer, so it stays a silent drop. The blocked check runs before anything touches the network, so Twt-Block cannot be sidestepped by re-registering. Silent mode stays silent, which is how every version before this behaved — except for an admin, who would otherwise have no way to turn registration back on. Then the gate the whole change rests on: before any outbound mail, the claimed address is resolved on its own server and the resolved key must equal the key that signed the request. Without it a signed Reply-To naming someone else's address would be enough to take their feed, and replying to the claimed smol:// URI would pin the sender's key as verified under that address — a permanent denial of service against whoever really owns it. Every reply is queued against the short form for the same reason; nothing hands a self-certifying URI to send. Registration attempts are counted before the dial rather than after, since one that fails its key check costs the same connection. A per-cycle dial budget bounds how long the state lock is held, and a mail over budget keeps its row instead of being dropped. Admin commands are recognised only from an admin address, and only Admit, Block and Registration count as admin commands — an admin's Twt-Help means Twt-Help. A non-admin sending one gets the ordinary answer and is never told admin commands exist. Rig still passes, so the owner path is unchanged in practice as well as in intent.
2026-10-14 20:21:11 +03:00
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
Twt-Nick: yournick change the name shown on your feed
Twt-Delete: <time> remove one post or repost
Twt-Repost: <mention> <#id> share someone else's post
Twt-Prune: fifo | off what happens when your feed fills up
feat: answer unknown senders, gated on proving they hold their address process now splits by whether the sender owns a feed. The owner path runs first, so a rotated owner is never mistaken for a registration, and behaves exactly as before for ordinary mail. The stranger path is the new one, and every branch of it is gated. No signed Reply-To means no address to verify or answer, so it stays a silent drop. The blocked check runs before anything touches the network, so Twt-Block cannot be sidestepped by re-registering. Silent mode stays silent, which is how every version before this behaved — except for an admin, who would otherwise have no way to turn registration back on. Then the gate the whole change rests on: before any outbound mail, the claimed address is resolved on its own server and the resolved key must equal the key that signed the request. Without it a signed Reply-To naming someone else's address would be enough to take their feed, and replying to the claimed smol:// URI would pin the sender's key as verified under that address — a permanent denial of service against whoever really owns it. Every reply is queued against the short form for the same reason; nothing hands a self-certifying URI to send. Registration attempts are counted before the dial rather than after, since one that fails its key check costs the same connection. A per-cycle dial budget bounds how long the state lock is held, and a mail over budget keeps its row instead of being dropped. Admin commands are recognised only from an admin address, and only Admit, Block and Registration count as admin commands — an admin's Twt-Help means Twt-Help. A non-admin sending one gets the ordinary answer and is never told admin commands exist. Rig still passes, so the owner path is unchanged in practice as well as in intent.
2026-10-14 20:21:11 +03:00
Twt-Help: send this list again
help-admin-section =
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
You're an admin here, so you can also send:
feat: answer unknown senders, gated on proving they hold their address process now splits by whether the sender owns a feed. The owner path runs first, so a rotated owner is never mistaken for a registration, and behaves exactly as before for ordinary mail. The stranger path is the new one, and every branch of it is gated. No signed Reply-To means no address to verify or answer, so it stays a silent drop. The blocked check runs before anything touches the network, so Twt-Block cannot be sidestepped by re-registering. Silent mode stays silent, which is how every version before this behaved — except for an admin, who would otherwise have no way to turn registration back on. Then the gate the whole change rests on: before any outbound mail, the claimed address is resolved on its own server and the resolved key must equal the key that signed the request. Without it a signed Reply-To naming someone else's address would be enough to take their feed, and replying to the claimed smol:// URI would pin the sender's key as verified under that address — a permanent denial of service against whoever really owns it. Every reply is queued against the short form for the same reason; nothing hands a self-certifying URI to send. Registration attempts are counted before the dial rather than after, since one that fails its key check costs the same connection. A per-cycle dial budget bounds how long the state lock is held, and a mail over budget keeps its row instead of being dropped. Admin commands are recognised only from an admin address, and only Admit, Block and Registration count as admin commands — an admin's Twt-Help means Twt-Help. A non-admin sending one gets the ordinary answer and is never told admin commands exist. Rig still passes, so the owner path is unchanged in practice as well as in intent.
2026-10-14 20:21:11 +03:00
Twt-Admit: user@host give someone a feed
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
Twt-Block: user@host stop someone posting
feat: answer unknown senders, gated on proving they hold their address process now splits by whether the sender owns a feed. The owner path runs first, so a rotated owner is never mistaken for a registration, and behaves exactly as before for ordinary mail. The stranger path is the new one, and every branch of it is gated. No signed Reply-To means no address to verify or answer, so it stays a silent drop. The blocked check runs before anything touches the network, so Twt-Block cannot be sidestepped by re-registering. Silent mode stays silent, which is how every version before this behaved — except for an admin, who would otherwise have no way to turn registration back on. Then the gate the whole change rests on: before any outbound mail, the claimed address is resolved on its own server and the resolved key must equal the key that signed the request. Without it a signed Reply-To naming someone else's address would be enough to take their feed, and replying to the claimed smol:// URI would pin the sender's key as verified under that address — a permanent denial of service against whoever really owns it. Every reply is queued against the short form for the same reason; nothing hands a self-certifying URI to send. Registration attempts are counted before the dial rather than after, since one that fails its key check costs the same connection. A per-cycle dial budget bounds how long the state lock is held, and a mail over budget keeps its row instead of being dropped. Admin commands are recognised only from an admin address, and only Admit, Block and Registration count as admin commands — an admin's Twt-Help means Twt-Help. A non-admin sending one gets the ordinary answer and is never told admin commands exist. Rig still passes, so the owner path is unchanged in practice as well as in intent.
2026-10-14 20:21:11 +03:00
Twt-Registration: open | closed | silent
## Admin notifications
admin-notify-subject = twtxt: new feed { $slug }
admin-notify-body =
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
Someone registered a feed.
feat: answer unknown senders, gated on proving they hold their address process now splits by whether the sender owns a feed. The owner path runs first, so a rotated owner is never mistaken for a registration, and behaves exactly as before for ordinary mail. The stranger path is the new one, and every branch of it is gated. No signed Reply-To means no address to verify or answer, so it stays a silent drop. The blocked check runs before anything touches the network, so Twt-Block cannot be sidestepped by re-registering. Silent mode stays silent, which is how every version before this behaved — except for an admin, who would otherwise have no way to turn registration back on. Then the gate the whole change rests on: before any outbound mail, the claimed address is resolved on its own server and the resolved key must equal the key that signed the request. Without it a signed Reply-To naming someone else's address would be enough to take their feed, and replying to the claimed smol:// URI would pin the sender's key as verified under that address — a permanent denial of service against whoever really owns it. Every reply is queued against the short form for the same reason; nothing hands a self-certifying URI to send. Registration attempts are counted before the dial rather than after, since one that fails its key check costs the same connection. A per-cycle dial budget bounds how long the state lock is held, and a mail over budget keeps its row instead of being dropped. Admin commands are recognised only from an admin address, and only Admit, Block and Registration count as admin commands — an admin's Twt-Help means Twt-Help. A non-admin sending one gets the ordinary answer and is never told admin commands exist. Rig still passes, so the owner path is unchanged in practice as well as in intent.
2026-10-14 20:21:11 +03:00
Address: { $address }
Feed: { $url }
Fingerprint: { $fingerprint }
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
If you don't know this person, check that fingerprint with them some
other way. Send Twt-Block to stop them posting.
## What an admin command did. These are sentences on their own: they become
## the subject and open the body.
admin-admitted = Gave { $address } a feed at { $url }.
admin-admit-failed = Couldn't give { $address } a feed. { $detail }
admin-blocked = Blocked { $address }. Their posts won't go up from now on.
admin-block-unknown = { $address } wasn't accepted here, so there was nothing to block.
admin-block-failed = Couldn't block { $address }. { $detail }
admin-block-half-done = Blocked { $address } here, but the server still has their token, so their posts may still go up. { $detail }
admin-registration-open = Anyone can register a feed now.
admin-registration-closed = Registration is closed. People who write in are told so.
admin-registration-silent = Registration is closed and people who write in get no reply.
feat: answer unknown senders, gated on proving they hold their address process now splits by whether the sender owns a feed. The owner path runs first, so a rotated owner is never mistaken for a registration, and behaves exactly as before for ordinary mail. The stranger path is the new one, and every branch of it is gated. No signed Reply-To means no address to verify or answer, so it stays a silent drop. The blocked check runs before anything touches the network, so Twt-Block cannot be sidestepped by re-registering. Silent mode stays silent, which is how every version before this behaved — except for an admin, who would otherwise have no way to turn registration back on. Then the gate the whole change rests on: before any outbound mail, the claimed address is resolved on its own server and the resolved key must equal the key that signed the request. Without it a signed Reply-To naming someone else's address would be enough to take their feed, and replying to the claimed smol:// URI would pin the sender's key as verified under that address — a permanent denial of service against whoever really owns it. Every reply is queued against the short form for the same reason; nothing hands a self-certifying URI to send. Registration attempts are counted before the dial rather than after, since one that fails its key check costs the same connection. A per-cycle dial budget bounds how long the state lock is held, and a mail over budget keeps its row instead of being dropped. Admin commands are recognised only from an admin address, and only Admit, Block and Registration count as admin commands — an admin's Twt-Help means Twt-Help. A non-admin sending one gets the ordinary answer and is never told admin commands exist. Rig still passes, so the owner path is unchanged in practice as well as in intent.
2026-10-14 20:21:11 +03:00
admin-ack-subject = twtxt: { $what }
admin-ack-body =
{ $what }
docs: rewrite the author-facing copy in plain language The copy was written for someone who had read the spec. It named UTF-8, RFC 3339, canonical form, code points and frontmatter fields, and the one place it gave instructions it gave them as fumi command lines, which say nothing to anyone using carrier or gsmol. Now it uses the words the clients put on screen: "header", with the "+ header" button both of them have, and "Requests folder", which is what both call it. One word per idea throughout — post, feed, header, Requests folder — and reasons are whole sentences that say what happened and what to do, so the reply no longer opens "Reason:" followed by a lowercase fragment. The first-contact mail is the one a stranger reads before anything else, so it now covers accepting this address: a recommendation, not the mechanism behind it. The mechanism would also have aged badly, since a client that delivers its token once makes accepting cost a single refused post rather than all of them. A refused post gets a detail paragraph where one line cannot hold the fix. It tells the author their token arrived and to send again, and what to do if it is refused a second time, which is what happens on a client that attaches the token to everything. A first post on an empty feed says so differently: the author is watching their first attempt fail and needs to hear the feed is fine. Seven admin strings were built with format! and never reached the catalogue, so an ack opened its body in lowercase and could not be translated. They are entries now, and say what blocking does rather than naming the mechanism. The cap warning said "90% of its 1048576 byte limit". It says 1 MB. Four rig assertions matched the old wording and now match the new. They have to read rendered mail, so they stay coupled to it in a way the unit tests, which assert on Reason variants, do not.
2026-10-15 09:06:54 +03:00
You're getting this because you're an admin here.