The public registry for devis.im — claim your own
name.devis.im subdomain and an optional name@devis.im email alias,
free, by opening a pull request.
A denizen is an inhabitant of a place. Claim your name and you're a denizen of devis.im.
One name gives you one identity on devis.im — the subdomain and the email alias share it.
| What | Where it goes | |
|---|---|---|
| 🌐 | yourname.devis.im |
A subdomain pointed wherever you like — GitHub Pages, Vercel, Netlify, a raw server, or a redirect. HTTPS automatic. |
| ✉️ | yourname@devis.im (optional) |
An email alias that forwards to your real inbox. People email the alias; it lands privately — your real address is never exposed. |
Claim
rajan→ bothrajan.devis.imandrajan@devis.imare yours.
flowchart LR
A(["You open a PR<br/>adding domains/name.json"]) --> B{"Automated<br/>checks"}
B -- fail --> A
B -- pass --> C["Maintainer review"]
C -- merge --> D[/"Provision bot"/]
D --> E(["name.devis.im live<br/>DNS + HTTPS, minutes"])
D -. "email enabled" .-> F(["Link to the<br/>private email form"])
style A fill:#0f766e,color:#fff
style E fill:#0f766e,color:#fff
style F fill:#e6f3f1,color:#0f766e
- Open a pull request adding one file:
domains/yourname.json. - Automated checks run on your file — fix anything they flag.
- A maintainer reviews. If the name is free and the request looks fine, it's merged.
- On merge, automation provisions your subdomain — DNS record + HTTPS within minutes.
- Asked for email? A bot comments on your merged PR with a private link to add your forwarding address.
Email is opt-in ("email": { "enabled": true }) and your real address is never in the repo.
flowchart TD
A(["Claim merged with<br/>email.enabled: true"]) --> B["Open the private form<br/>claim.devis.im"]
B --> C{"Verify with GitHub"}
C -- "not your name" --> X(["Rejected"])
C -- "you own it" --> D["Submit forwarding address<br/>behind Turnstile"]
D --> E["Cloudflare emails that<br/>inbox a verification link"]
E --> F(["You click it"])
F --> G(["name@devis.im -> your inbox<br/>forwarding live"])
style A fill:#0f766e,color:#fff
style G fill:#0f766e,color:#fff
style X fill:#fdeaea,color:#8a1f1f
Why your forwarding address is not in the pull request — this repo is public;
anything in your file and its git history is visible forever. The whole point of
yourname@devis.im is to hide your real address, so:
- The public file only carries
email.enabled: true|false. - After merge, a bot comments on your PR with a private link to the form (
claim.devis.im, name prefilled). - You verify with GitHub there — proving the name is yours, so nobody can point your alias at their inbox.
- Then you submit the real forwarding address (behind Turnstile).
- Cloudflare sends a verification link to your inbox; you click it; forwarding goes live.
- The registry stores nothing — your address lives only in Cloudflare's verified-destination system.
New to pull requests? Follow the slow, no-assumptions walkthrough in Your first claim, step by step. The short version:
- Fork this repository.
- Create
domains/<yourname>.json. The filename is the name you're claiming —rajan.jsonclaimsrajan.devis.imandrajan@devis.im. Lowercase letters, numbers, and hyphens only. - Fill it in using the format below. Keep
"$schema": "../schema.json"at the top so your editor validates as you type. - Set
owner.githubto your own GitHub username — it must match the PR author. - Open a pull request. Automated checks run; fix anything they flag.
- Wait for review. On merge, your subdomain is set up automatically.
{
"$schema": "../schema.json",
"owner": {
"github": "your-github-username"
},
"record": {
"CNAME": "your-github-username.github.io"
},
"email": {
"enabled": true
}
}| Field | Required | Description |
|---|---|---|
owner.github |
✅ | Your GitHub username. Must match the PR author. |
owner.email |
— | A public contact email. Never your private forwarding address. |
record |
✅ | Where the subdomain points (see record types below). |
email.enabled |
— | true if you also want name@devis.im forwarding. Omit for subdomain only. |
proxied |
— | Route through Cloudflare's proxy. Defaults to false. |
Pick whichever fits how your site is hosted. Use CNAME or A/AAAA, not both.
| Type | Value | Use for |
|---|---|---|
CNAME |
a single hostname | GitHub Pages, Vercel, Netlify, most hosts |
A |
array of IPv4 addresses | a raw server with an IPv4 address |
AAAA |
array of IPv6 addresses | a raw server with an IPv6 address |
TXT |
a string or array of strings | verification records, etc. |
URL |
a URL | redirect the subdomain elsewhere — not supported yet (claims using it are rejected) |
Validation rejects, with a clear message: a CNAME set to an IP (use A/AAAA),
A/AAAA addresses that aren't public (no loopback, private, link-local, or
multicast), empty record blocks, and the wrong IP family for the type.
Some names can't be claimed — DNS infrastructure (www, ns1, mail…), email role
addresses (abuse, postmaster, admin, security, dmarc…), and a handful of
reserved service words. The full list is in reserved.json. These stay
with devis.im so system mail and abuse reports always reach the operators, never a third party.
One person, up to 2 names. Each GitHub account can hold at most 2 claims — release one (delete its file in a PR) to free a slot.
| When it's live | |
|---|---|
| 🌐 Subdomain | Within minutes of merge — HTTPS included. |
| Once you submit your forwarding address through the private form, verify with GitHub, and click the link Cloudflare emails your inbox. That click is required and only you can do it — it also proves you control the destination inbox. |
Parked names don't resolve. There's no wildcard DNS — an unclaimed
name.devis.imreturns NXDOMAIN. A name is live only once its claim is merged.
Fire-and-forget — there's no dashboard:
- Change where your subdomain points, or your forwarding target → open a new PR editing your file.
- Release a name → delete your file in a PR. On merge, automation tears down the DNS record + email routing rule, and the name returns to the pool. This also frees a slot against the 2-name cap.
At the 2-name cap and want a different name? Release first, then claim — they're two separate PRs (one file per PR), so the delete must merge before the new claim passes.
Subdomains or aliases used for phishing, malware, spam, or impersonation are removed without
notice. Report abuse to abuse@devis.im. See the operator runbook in
docs/abuse-triage.md.
Security vulnerabilities go through a private channel instead — see SECURITY.md.
No dashboard, no database. The repository is the state, and four pieces of automation act on it.
| When | What runs | What it does |
|---|---|---|
| Pull request | validate.yml → scripts/validate-claim.rb |
Validates your file against schema.json plus the policy rules the schema can't express — one file per PR, filename matches the claimed name, name free and not reserved, owner.github equals the PR author, per-owner cap, record sanity. Posts a single pass/fail comment, updated in place on every push. |
Merge to main |
provision.yml → scripts/provision.rb |
Diffs added/changed/deleted domains/*.json and applies them to Cloudflare idempotently — creates or updates DNS records, tears them down (plus any routing rule) on delete, and comments the private email-form link when email.enabled. One bad file never breaks the batch. |
| Email setup | worker/ (Cloudflare Worker + Pages form) |
The private intake at claim.devis.im: GitHub sign-in proves the name is yours, Turnstile blocks bots, then it creates the verified destination address + routing rule. Stores nothing. |
| Weekly cron | blocklist.yml → scripts/blocklist-check.rb |
Checks devis.im against Spamhaus DBL / SURBL / URIBL and opens a tracking issue on a real listing. Resolver-blocked answers are reported unknown, never a false hit. |
Two rules hold the whole design together: your forwarding address never touches this repo, and every provisioning step is idempotent — re-running on the same name updates, never duplicates.
domains/ one JSON file per claimed name (example.json is a template)
schema.json the contract every claim validates against
reserved.json names that can't be claimed
scripts/ Ruby automation — validate, provision, blocklist check
spec/ RSpec suite for those scripts
.github/ workflows, issue + PR templates, CODEOWNERS
worker/ TypeScript Cloudflare Worker + the private email form
site/ static landing page for the apex devis.im
docs/ operator runbooks + the newcomer claim walkthrough
Only needed if you're changing the automation — claiming a name needs nothing installed.
bundle install # Ruby side
bundle exec rspec # validator, provisioner, blocklist specs
cd worker && npm install && npm test # Worker specs (vitest)Ruby scripts are classes (Validator, Provisioner) with a guarded CLI entrypoint. Change behaviour,
change the spec. Secrets live in GitHub Secrets and Worker env vars — never in a tracked file.
Registry, validation, provisioning, the email Worker, and the landing page are all shipped and running. Known gaps, so nothing surprises you:
URLrecords aren't supported yet — claims using one are rejected rather than merged into a dead name. Redirect support is a follow-up.- Wildcard/parked names return NXDOMAIN by design. No interstitial page.
- Forward-only email. No mailboxes; you can't send as
name@devis.im. - DMARC is ramping through
none→quarantine→reject, perdocs/email-reputation.md.
Sequencing and what's left lives in ROADMAP.md.
| For | Where |
|---|---|
| Claim help, bugs, ideas | Open an issue — templates exist for claim help, abuse reports, and name releases |
| Abuse | abuse@devis.im |
| Security | Private advisory, see SECURITY.md |
| Anything else | irajanbhattarai@gmail.com |
Names are free and stay free. The domain renewal and operator time aren't — if devis.im is
useful to you, FUNDING.md explains what support covers and what it deliberately
doesn't buy (no paid tiers, no priority claims, no reserved-name sales).
MIT © Rajan Bhattarai