Getting Started#
In this tutorial we're going to walkthrough using r3ply from start to finish with an example. We will be installing the re CLI tool, generating a config, simulating a comment, and then discussing next steps. Follow the the steps below from within your project's top-level directory.
Table of Contents
- Comments 💬
Installation & Setup#
For this tutorial we will need to install the r3ply CLI tool called re.
# use npm -D @r3ply/cli for per project installations
npm -g @r3ply/cli
re --help
You should see the usage statement print.
Next initialize a new r3ply project at the top-level directory of your project.
re init
You should see output similar to this (but not exactly the same):
Initialized empty r3ply project at /Users/demo/Developer/r3ply/site
Add the following site entry to your config:
[[site]]
domain = "site.local.test"
r3ply = "cli.r3ply.test"
signet = "6Be8MUKnqpXZ73MDbX2u2g"
issued = "2025-11-08"
label = "CLI"
Help: You can generate a config with `re generate config` if you have not already.
This is a site entry, and in r3ply there's one site entry per domain x r3ply pair.
In this case, the domain is "site.local.test" and r3ply is "cli.r3ply.test". These values are special cases used by the r3ply CLI.
The signet is a special cryptographic envelope (docs) that signifies that unique domain x r3ply pair. In this case, the signet is issued by the r3ply CLI to your local project (as indicated by label).
Great, now our r3ply project is initialized. Don't worry about saving the initialization output above. We will see it again in the next step.
Generating a Config#
Now let's generate a config so we can use r3ply.
re generate config
You should see output similar to this:
version = "0.0.1"
enabled = true
[[site]]
domain = "site.local.test"
r3ply = "cli.r3ply.test"
signet = "6Be8MUKnqpXZ73MDbX2u2g"
issued = "2025-11-08"
label = "CLI"
[comments.email]
enabled = true
[moderation]
enabled = true
github = [ ]
webhook = [ ]
[[moderation.local]]
"file_path_{}" = "comment_{{ comment.id[:8] }}.json"
enabled = true
"allow*" = [ ]
(If you look closely you can see that the [[site]] entry here is identical to the one from the re init command we ran earlier)
Copy the above output to a file named config.toml, and save the file in a place where it can be accessed from your website. r3ply will look at the following paths in this order:
# Priority from highest to lowest:
https://${domain}/.well-known/r3ply/config.toml
https://${domain}/.well-known/r3ply.config.toml
https://${domain}/r3ply.config.toml
https://${domain}/r3ply.toml
(The r3ply website itself stores the config at static/.well-known/r3ply/config.toml and can be reached online at https://r3ply.com/.well-known/r3ply/config.toml.)
Finally, let's set the path of your config as the default config path:
re config set-default <your-config-path>
We can run re config validate to verify that our config is well formed. If there's no output then you're ready to proceed to the next section and begin simulating email comments.
Simulating a Comment#
re simulate email --moderate
You should see a large amount of text representing each stage of the comment processing pipeline. The docs cover output more in-depth, but it's basically the entire email to comment pipeline broken into stages.
The CLI docs also cover how to silence and filter this output. Check it out later.
The --moderate flag told re to simulate email to comment pipeline, and then to send that comment for moderation. Towards the bottom of the output you should see something similar to this (but not exactly the same):
1 # === Moderation: Local[0] ===
2
3 #################################
4 # Request portion of moderation #
5 #################################
6
7 # `bypass` asks to skip moderation altogether. For local moderation it has no effect.
8 [request]
9 type = "local"
10 bypass = false
11
12 # `relative_path` is relative to project root.
13 [request.args]
14 relative_path = "content/comments/b69922e4.json"
15 # `comment` variable elided, see comment output from earlier steps in docs
16 comment = '[elided... see "Comment: Processed" above]'
17
18 ################################
19 # Ticket portion of moderation #
20 ################################
21
22 # `ticket.local` is the response to a request for local moderation.
23 [ticket.local]
24 absolute_path = "/Users/demo/Developer/r3ply/site/content/comments/b69922e4.json"
At the bottom we see the absolute_path the comment was written to. You can change this path by editing your config (file_path_{} under [[moderation.local]]).
What's Inside a Comment#
Now that we can simulate the receiving comments based on a real configuration, we need to understand what's inside a comment object. Open the file that was at the absolute_path from the last step. You can also expand the one that was used during the making of this tutorial.
Expand to See File Close File Details
1 {
2 "r3ply": {
3 "config_version": "0.0.1",
4 "server": "cli.r3ply.test",
5 "site": "site.local.test",
6 "signet": "wWM5hk4DKr1xVRhVq-7aog",
7 "issued": "2025-10-16"
8 },
9 "author": {
10 "pseudonym": "2ec68974e2f82e9bd891a351eefe4bbeefe2670b745c861df31c975e54c207c1",
11 "token": "kktE_W_Nlh95kjQpAbbcDkpOPtTjh8SRJNAdulGWav5Nv0zJNUABG91PMIeTo8K6PyMXkHp8iJsxuR-Qg0rFwKLk3LmZt0NTJ1SNUOLL8-0k0Ik-bNSBWCnH_lRCkWFc7LRpTfPNurZ7ncifRVFGbqgKrFoLhvwGSujQivorr9tNKq_r7C2aTyb-ECmTWJdgWVHaD4lwetqv0tU-tueGkBlbTHWlAR6JUX2UwOrQrTSgzx6Ft3-hb4Q9esLhlN1ffUK43Ov0E8dhGReH-Uy1fj2k_EzyOwLLfZ771mkfC4dMsjPl0jMZTSjDQqP-tK3hiA5xJsC6Aa00S04ZFVXBIZVNHEgds4AbcfUhpZqwOfBLfCXey4scQBW5DZFGkF3Km3_gaBJUYKTaYoYLN71Xd5rjELcpahwzvxUurUoNYQn-D6zt_U-Fbt4SeoA9370ivV1U0HeY6w-5YWrk"
12 },
13 "comment": {
14 "id": "b69922e4da6e45cf9cd75cc3b878fc5c",
15 "ts_rcvd": "1761144137",
16 "subject": {
17 "url": "https://site.local.test/reviews/toughest-fights-in-monkey-island",
18 "origin": "https://site.local.test",
19 "protocol": "https:",
20 "hostname": "site.local.test",
21 "path": "/reviews/toughest-fights-in-monkey-island"
22 },
23 "txt": "Is it possible to run a startup successfully without Investor/funding. Is there any platform where I could contribute to build a project based on ongoing research, like implementing a research paper?",
24 "md": "<p>Is it possible to run a startup successfully without Investor/funding. Is there any platform where I could contribute to build a project based on ongoing research, like implementing a research paper?</p>\n",
25 "html": "<p>Is it possible to run a startup successfully without Investor/funding. Is there any platform where I could contribute to build a project based on ongoing research, like implementing a research paper?</p>\n"
26 },
27 "email": {
28 "to": "[email protected]",
29 "subject": "https://site.local.test/reviews/toughest-fights-in-monkey-island",
30 "date": "2018-08-26T07:24:01+00:00",
31 "text": "Is it possible to run a startup successfully without Investor/funding. Is there any platform where I could contribute to build a project based on ongoing research, like implementing a research paper?",
32 "auth": {
33 "dkim": false,
34 "spf": false,
35 "dmarc": false,
36 "pass": false
37 },
38 "from": {
39 "pseudonym": "2ec68974e2f82e9bd891a351eefe4bbeefe2670b745c861df31c975e54c207c1",
40 "signet": "wWM5hk4DKr1xVRhVq-7aog",
41 "issued": "2025-10-16",
42 "token": "kktE_W_Nlh95kjQpAbbcDkpOPtTjh8SRJNAdulGWav5Nv0zJNUABG91PMIeTo8K6PyMXkHp8iJsxuR-Qg0rFwKLk3LmZt0NTJ1SNUOLL8-0k0Ik-bNSBWCnH_lRCkWFc7LRpTfPNurZ7ncifRVFGbqgKrFoLhvwGSujQivorr9tNKq_r7C2aTyb-ECmTWJdgWVHaD4lwetqv0tU-tueGkBlbTHWlAR6JUX2UwOrQrTSgzx6Ft3-hb4Q9esLhlN1ffUK43Ov0E8dhGReH-Uy1fj2k_EzyOwLLfZ771mkfC4dMsjPl0jMZTSjDQqP-tK3hiA5xJsC6Aa00S04ZFVXBIZVNHEgds4AbcfUhpZqwOfBLfCXey4scQBW5DZFGkF3Km3_gaBJUYKTaYoYLN71Xd5rjELcpahwzvxUurUoNYQn-D6zt_U-Fbt4SeoA9370ivV1U0HeY6w-5YWrk"
43 }
44 }
45 }
Let's look more closely at individual items to get a better understanding.
...
"r3ply": {
"config_version": "0.0.1",
"server": "cli.r3ply.test",
"site": "site.local.test",
"signet": "wWM5hk4DKr1xVRhVq-7aog",
"issued": "2025-10-16"
},
...
This is just metadata about concerning the site, r3ply server, etc... that serviced this comment.
Next let's look at author:
...
"author": {
"pseudonym": "2ec68974e2f82...",
"token": "..."
},
...
Here we see details about the comment's author. Their email address has been anonymized to a stable pseudonym that can be used like an ID. There's also a long token which isn't shown fully here (You can read more about these in the docs, but it isn't necessary right now).
...
"comment": {
"id": "b69922e4da6e45cf9cd75cc3b878fc5c",
"ts_rcvd": "1761144137",
"subject": {
"url": "https://site.local.test/reviews/toughest-fights-in-monkey-island",
"origin": "https://site.local.test",
"protocol": "https:",
"hostname": "site.local.test",
"path": "/reviews/toughest-fights-in-monkey-island"
},
"txt": "Is it possible to run a startup successfully without Investor/funding. Is there any platform where I could contribute to build a project based on ongoing research, like implementing a research paper?",
"md": "<p>Is it possible to run a startup successfully without Investor/funding. Is there any platform where I could contribute to build a project based on ongoing research, like implementing a research paper?</p>\n",
"html": "<p>Is it possible to run a startup successfully without Investor/funding. Is there any platform where I could contribute to build a project based on ongoing research, like implementing a research paper?</p>\n"
},
...
Here's the actual comment object. There're three nearly identical versions of the comment body: txt, md, and html. This is because r3ply supports text written as markdown, as well as converting that markdown to HTML, but it will also strip out malicious html tags. You can configure this further within the [comment] object (docs).
There's also the subject field of the comment object, which tells us the URL of what the comment was in response to. Using this you should be able to identify the page the comment belongs on.
Integrating Comments in Your Site#
To take the comments and build them into your site you just treat them like you would do any other content. Since everyone's websites are built differently, specific advice can't be given, however r3ply allows you to customize how comments look using a templating language. Therefore you have a few options at your disposal. The choice is yours.
- You can just save comments as plain json files and build your site from those
- or you can customize it in a way that works with how you would like them to be built into your site.
Let's look at a quick example though, using the comment from above. We could render that comment as HTML as follows:
<article data-comment-id="48ec61da69b743cda2d6747efe6dca80">
<header>
<time datetime="2025-11-08T14:58:08+00:00">26 August, 2025</time>
<span> - </span>
<strong>5b4f46b</strong> 🗣️
</header>
<section>
<blockquote>
<p>I would appreciate the advice. Top 10 reasons or even top 5 reasons. What are your favorite tech and non-tech podcasts?</p>
</blockquote>
</section>
<hr>
<nav>
<a href="/reviews/guybrushs-best-comebacks">View related post</a>
<a href="/commenters/5b4f46b5/">More posts by user</a>
</nav>
</article>
And that same comment above would render like this (with a little added styling):
Is it possible to run a startup successfully without Investor/funding? Is there any platform where I could contribute to build a project based on ongoing research, like implementing a research paper?And if yes, are there any conceivable reasons for a startup to be a bank startup?
To get something like the above example you can add the comment_{} variable (under [comments]):
1 version = "0.0.1"
2 enabled = true
3
4 [[site]]
5 domain = "site.local.test"
6 r3ply = "cli.r3ply.test"
7 signet = "6Be8MUKnqpXZ73MDbX2u2g"
8 issued = "2025-11-08"
9 label = "CLI"
10
11 [comments.email]
12 enabled = true
13 "comment_{}" = """
14 <article data-comment-id="{{ comment.id }}">
15 <header>
16 <time datetime="{{ email.date }}">{{ email.date | date(format="%d %B, %Y") }}</time>
17 <span> - </span>
18 <strong>{{ author.pseudonym[:7] }}</strong> 🗣️
19 </header>
20 <section>
21 <blockquote>
22 {{ comment.html }}
23 </blockquote>
24 </section>
25 <hr>
26 <nav>
27 <a href="{{ comment.subject.path }}">View related post</a>
28 <a href="/commenters/{{ author.pseudonym[:8] }}/">More posts by user</a>
29 </nav>
30 </article>
31 """
32
33 [moderation]
34 enabled = true
35 github = [ ]
36 webhook = [ ]
37
38 [[moderation.local]]
39 "file_path_{}" = "comment_{{ comment.id[:8] }}.json"
40 enabled = true
41 "allow*" = [ ]
To get more ideas on what's possible, check out the demo section of the website.
Summary & Next Steps#
You should now able to simulate comments via email with re simulate email, and render them using your site's build pipeline.
The development process from here is going to be just iterating on the steps above, using the re CLI tool, until you come up with something that you like. There's a lot of helpful functionality waiting to be discovered in the config and CLI sections of the docs.
When you're ready, you can deploy your site online to receive comments publicly, via email.
To do that you're going to need to add a new site entry for your site's public domain:
# each (site x r3ply) pair has an entry
[[site]]
domain = "example.com"
r3ply = "r3ply.com"
signet = "iSQIIBcF7ka2UURJpFDkYw"
issued = 2025-08-26
Next, you'll want to add another moderation channel to your r3ply config. In this tutorial we only covered the local moderation channel. For example you can add GitHub moderation like so:
# E.g. add moderation by GitHub PR
[[moderation.github]]
owner = "<ACCOUNT>"
repo = "<REPO>"
"file_path_{}" = "content/comments/{{ comment.id[:8] }}.md"
Finally, read the config and CLI docs to take full advantage of r3ply.
Comments (0) #
Commenting Info:
This is a demo of commenting using r3ply. You can leave comments on this website by sending an email.
Your email address will be anonymized, and can never be shared with anyone.
To try it out click the Write Comment button, or draft an email manually 1. Your email client with a template already filled out that looks as follows:
To send an email manually, just compose it exactly like the code snippet above. To respond to a comment append
#<Comment-ID>to the path, e.g.Subject: /docs/getting-started/#abcd1234(please note the trailing slash). You can also respond to text fragments the same way, e.g.Subject: /docs/getting-started/#:~:text=You%20should%20see%20a%20bunch%20of%20text%20representing. ↩Pending Comments (0)
This comment shouldn't actually normally be rendered. It's used to by the base.html template to render one instance of a templat…-
root
/
-
-
parent
..
-
-
#
.
-
next
⭣
-
-
prev.
⭡
-
This comment shouldn't actually normally be rendered. It's used to by the base.html template to render one instance of a template that's wrapped in
<template>tags. That template is then later cloned and appended to the page for each comment that's pending in the comment cache. To view this comment go to /debug-comment-template/.