RunHacks SMTP and IMAP mail access
Your RunHacks mailbox, in your own mail client or your own script. Create an API key, use it as the password, send and read over TLS.
What this is
RunHacks runs a mail server on mail.runhacks.sh. It speaks real SMTP for sending and real IMAP for reading. Point Thunderbird, mutt, or your own script at it and it behaves the way a mail server behaves, because it is one. It serves the same mailbox the Mail app shows you inside RunHacks OS: challenge characters send you mail there, and mail you send from a client reaches them exactly as if you had used the Mail app.
It is a closed system
RunHacks mail is sealed off from the public mail network in both directions. There is no MX
record for runhacks.sh, so nothing on the internet can route mail here, and the
server has no outbound relay, so nothing you send can leave. Mail from your Gmail will never
arrive in your RunHacks inbox, and you cannot mail your Gmail from your RunHacks mailbox.
Every address on both ends of a message has to exist inside RunHacks: your own mailbox, another
player's, or a character at a challenge domain that has been opened up to your account. Anything
else is refused outright at RCPT TO rather than accepted and quietly dropped, so you
always find out immediately. See Limits for the exact
refusal.
This is deliberate. A closed loop means you can be as aggressive as you like with the mail protocol, spoofing headers, forging senders, pretexting and phishing a character, without any of it escaping to reach a real person.
Your username is the part before @runhacks.sh, the same name you use for
SFTP. Your password is a RunHacks API key. There is no separate
mail password to forget.
Ports
Four ports, one server behind all of them:
Host mail.runhacks.sh SMTPS 465 implicit TLS send recommended SMTP 2525 plaintext send IMAPS 993 implicit TLS read recommended IMAP 1143 plaintext read
Use 465 and 993. The plaintext ports are not a fallback for when TLS is inconvenient, they are
there so you can watch the protocol go by with nc and read what a mail server
actually says. Anything you do on them, including your API key, crosses the network in the
clear.
The TLS ports are implicit TLS: the connection is encrypted from the first byte. There is no STARTTLS. If your client offers "STARTTLS" and "SSL/TLS" as options, pick SSL/TLS. A client set to STARTTLS on port 465 will hang, and a client set to STARTTLS on 2525 will either fail or quietly send your credentials unencrypted.
Get an API key
Open RunHacks OS, press alt+k for the Keys app, press n, and name the key after the thing that will use it. The name is what you will use later to work out which key to revoke.
The secret appears once, on the screen right after you create it. Nothing on RunHacks can show it to you again, because we do not store it in a readable form. Copy it then, or create another key later.
One key per client, for the same reason as one SSH key per device: a key that exists in exactly one place can be revoked without breaking anything else you own.
Your API key is a password
RunHacks will never ask you for an API key, and neither will any legitimate service. Not support, not a password reset, not a "verification" step, not a challenge character who sounds like they have authority. If anything asks you for one, that is the attack, and noticing it is the skill.
A key looks like rh_ followed by a long random string. Anything holding one can
read your mail and send mail as you. Keep it out of source control, out of screenshots, and out
of the challenge sites you are testing. If one leaks, revoke it in the Keys app with
x and create a replacement.
Verify the server
The TLS ports present a certificate for mail.runhacks.sh issued by Let's Encrypt.
Your client checks it for you, which is most of the value of using them. Do not click through a
certificate warning to reach a mail server you are about to hand a credential to.
Look at what the server is presenting before you trust it:
openssl s_client -connect mail.runhacks.sh:465 -servername mail.runhacks.sh
The chain should end at ISRG Root X1 and the subject should be
mail.runhacks.sh. Certificates rotate, so the fingerprint is not a fixed value you
can memorize the way the SSH host key is. Trust the chain, not
a fingerprint you wrote down last month.
Configure a mail client
Thunderbird, Account Settings, Add Mail Account, then Configure Manually:
Incoming IMAP mail.runhacks.sh 993 SSL/TLS Normal password Outgoing SMTP mail.runhacks.sh 465 SSL/TLS Normal password Username your-username (the name only, not the full address) Password rh_your_api_key
Apple Mail, Add Account, Other Mail Account. Enter
you@runhacks.sh and your API key as the password, then when it asks, choose IMAP
and fill in the same hosts and ports as above. Apple Mail will try to guess the settings first
and will guess wrong, so expect to correct it.
mutt, in ~/.muttrc:
set imap_user = "your-username" set imap_pass = "rh_your_api_key" set folder = "imaps://mail.runhacks.sh:993/" set spoolfile = "+INBOX" set record = "+Sent" set trash = "+Trash" set postponed = "+Archive" set smtp_url = "smtps://your-username@mail.runhacks.sh:465/" set smtp_pass = "rh_your_api_key" set ssl_force_tls = yes
Whatever the client, the two settings people get wrong are the same two: the username is the name only and not the full address, and the security setting is SSL/TLS and not STARTTLS.
Send from code
Python, with SMTP_SSL for implicit TLS:
import smtplib
from email.mime.text import MIMEText
msg = MIMEText("Your message body")
msg["Subject"] = "Subject line"
msg["From"] = "you@runhacks.sh"
msg["To"] = "recipient@runhacks.sh"
with smtplib.SMTP_SSL("mail.runhacks.sh", 465) as server:
server.login("your-username", "rh_your_api_key")
server.send_message(msg)
Node, with nodemailer. Note secure: true, which is what selects implicit TLS:
const nodemailer = require("nodemailer");
const transporter = nodemailer.createTransport({
host: "mail.runhacks.sh",
port: 465,
secure: true,
auth: { user: "your-username", pass: "rh_your_api_key" },
});
await transporter.sendMail({
from: "you@runhacks.sh",
to: "recipient@runhacks.sh",
subject: "Subject line",
text: "Your message body",
});
swaks, for one-off probing from a terminal:
swaks --to recipient@runhacks.sh \
--from you@runhacks.sh \
--server mail.runhacks.sh:465 --tlsc \
--auth LOGIN --auth-user your-username --auth-password rh_your_api_key \
--header "Subject: Test" --body "Your message"
Go, with tls.Dial, since smtp.SendMail assumes STARTTLS:
conn, err := tls.Dial("tcp", "mail.runhacks.sh:465", nil)
c, err := smtp.NewClient(conn, "mail.runhacks.sh")
c.Auth(smtp.PlainAuth("", "your-username", "rh_your_api_key", "mail.runhacks.sh"))
Read from code
import imaplib
imap = imaplib.IMAP4_SSL("mail.runhacks.sh", 993)
imap.login("your-username", "rh_your_api_key")
imap.select("INBOX")
status, messages = imap.search(None, "ALL")
for num in messages[0].split():
status, data = imap.fetch(num, "(RFC822)")
print(data[0][1].decode())
imap.logout()
There is no IDLE, so a client that wants to know about new mail has to poll. Poll on a sane interval. A tight loop against the mailbox is the kind of automated hammering the Rules of Engagement ask you not to do.
What the server supports
Ask it yourself, which is a better habit than trusting this page:
printf 'a1 CAPABILITY\r\na2 LOGOUT\r\n' | openssl s_client -quiet \
-connect mail.runhacks.sh:993 -servername mail.runhacks.sh
It answers IMAP4rev1 AUTH=PLAIN AUTH=LOGIN SASL-IR LITERAL+. Four mailboxes exist
and no others can be created: INBOX, Sent, Trash, and
Archive. Flags that persist are \Seen, \Flagged, and
\Deleted. Deleting moves a message to Trash, and EXPUNGE on Trash is what actually
removes it.
On the SMTP side, EHLO reports AUTH LOGIN PLAIN and advertises a SIZE of 32 MiB.
That number comes from the SMTP library's default and is not the limit that applies to you.
The real limit is below.
Limits
Message size 5 KiB subject plus body, per message Recipients 20 to, cc and bcc combined SMTP connections 4 per minute Recipient domains runhacks.sh, plus any a challenge opens up
The size limit is small on purpose. This is a mailbox for talking to challenge characters, not
for moving files. Over it, the server answers
554 5.0.0 Failed to deliver: Message too large with the byte count it measured.
Mail to an address outside the allowed domains is refused at RCPT TO with
550 5.1.1 Recipient domain not allowed, and the refusal lists the domains that
are currently allowed. That list is the authoritative answer, not this page: challenges add
their own fiction domains to it as you unlock them.
Mail to another player at @runhacks.sh lands in their inbox. Mail to a character at
an allowed challenge domain is delivered into the fiction, with no copy in your Sent folder's
counterpart on the other side. Every other address, including your own Gmail, a colleague, or a
real company, is refused, because there is nowhere outside RunHacks for it to go.
Reading the refusals
The server tries to tell you what went wrong. The codes worth recognizing:
235 authentication accepted 250 message accepted 421 rate limited, try again later 530 you have not authenticated yet 535 authentication failed 550 recipient domain not allowed, or no such user 554 accepted the envelope, refused the message
A 535 almost always means one of three things: the key is revoked, you pasted the
key with a trailing newline, or you used your full address as the username instead of the name
part. Check the Keys app for the first, and your client config for the other two.
Revoking a key
Open RunHacks OS, press alt+k for the Keys app, select the key by the name you gave it, and press x. Anything still using it stops working immediately: SMTP, IMAP, and API calls all authenticate with the same key.
What we store
Per key: the name you gave it, the visible prefix, when you created it, when it was last used, and a hash of the secret. We do not have the secret itself and cannot recover it for you.
Your mail is stored the way mail is stored: we can see it, and so can the challenge engine that scores you. Treat the mailbox as a game surface rather than a private channel, and do not put anything in it you would not want us to have.
In scope
mail.runhacks.sh is in scope for your own account, on the same terms as the file server. Inspect the protocol, see what the server refuses and how it refuses it, try to make it accept something it should not, and try to make it hand you something that is not yours.
Other players' mailboxes are not in scope. Player mailboxes are real mailboxes. If you find a way to read one, or to send mail as somebody else, that is a real vulnerability rather than a flag. Stop and mail security@runhacks.sh.
RunHacks OS v1.0 · kernel rh-tui 0.9.2 · build 2026.08.25 · about RunHacks