Lightweight and easy-to-use blog comments system, low friction, easy to self-host, GPLv3, respects users
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
forest 387a0d4d94 add powered by link 2 years ago
readme tone down the selected radio button highlight 3 years ago
static add powered by link 2 years ago
.gitignore tweaks to the submit form 3 years ago
Dockerfile go 16, fix log messages and fix captcha being too small 3 years ago ReadMe and GPLv3 3 years ago split captcha API url and captcha public URL 3 years ago
admin.html.gotemplate implementing the admin api in the golang app 3 years ago add powered by link 2 years ago
go.mod go 16, fix log messages and fix captcha being too small 3 years ago
go.sum go 16, fix log messages and fix captcha being too small 3 years ago
main.go add powered by link 2 years ago

SequentialRead Comments

Lightweight and easy-to-use blog comments system, low friction, easy to self-host, GPLv3 Licensed, respects users.

Table of Contents

  1. Features
  2. Screenshots
  3. Configuration


Easy to Implement

  • No need to set up a database, it uses the BoltDB embedded database
  • Docker images provided for amd64, arm64, and armv7 based machines running Linux
  • Embed in any HTML page with just a <div> and a <script> tag
  • Email notification feature is optional

Respects Users

  • Gravatar is supported, commenter decides whether to use it or not. Off by default.
    • When Gravatar is turned on, the avatar is fetched only once by the server and cached in the DB.
  • Commenter chooses whether or not receive email notifications on subsequent replies
  • All email notifications come with two opt-out links, one for the document in question, and one for the entire app
  • Uses 💥PoW! Captcha, a Proof-of-Work-based alternative to tracking & analytics captchas like Google's ReCaptcha.

Simple, but not TOO Simple

  • No Account or email address required to comment
    • Email address field provides some form of persistent identity if it is used
  • The comment body is the only required field
  • Markdown & HTML is supported inside the comment body
    • The HTML is sanitized before being displayed
    • All other dynamic fields use the .textContent DOM property to prevent XSS attacks
  • Admin email notifications & web-based Admin interface allows for basic moderation




Post Comment Form

post form

Notification Email

Notification emails are sent with both plain text and HTML for best compatibility an readability:

rich text email

And here is the HTML rich text email:

rich text email

Admin Interface

Yes, beautiful, I know :D

admin page


SequentialRead Comments is configured via environment variables.

Note that when the application is started, the current working directory must contain the static and data folders as well as the admin.html.gotemplate file. Otherwise the application will not work properly.

If you run SequentialRead Comments inside a Docker or other type of Linux container, you will want to mount the data folder to some sort of persistent volume so you don't lose all of your users' comments when the container has to be replaced/upgraded!


Which TCP port the server should listen on. Must be an Integer.


The full URL to the root of where the application is served from. For example If COMMENTS_BASE_PATH is set, for example, to /comments, then it might be something like

This variable is required for the email notification feature.


The list of origins from which the server will tell Web Browsers to allow Cross-Origin requests. If you host the comments server on a different domain from the pages it is used on, you must set this variable.

You would set it to a comma-delimited list of origins, like,,,


The hexadecimal string which was obtained by creating a new API token on the 💥PoW! Captcha server


Used to contact the captcha server & get captcha challenges


Used to load the captcha JavaScript and CSS into the page


This controlls how difficult the Proof of Work required to solve the captcha will be. The recommended value is 5 or 6. Each time you increase the difficulty level by one, it doubles the average amount of time the captcha will take.





These are standard SMTP submission connection & authentication parameters. They are all required to be set for the email notification feature to work.

If COMMENTS_EMAIL_PORT is set to 465, then "normal" SMTP will be used, that is, SMTP+STARTTLS. Otherwise, SMTP-wrapped-inside-TLS (SMTPS) will be used.

If you don't know what to put here, you can use a gmail account for this in a pinch. I would recommend creating a separate dedicated account for notifications. To set it up, use:

  • set COMMENTS_EMAIL_USER to your gmail email address
  • set COMMENTS_EMAIL_PASSWORD to your gmail password

In order for this to work, the gmail account you are using cannot have multifactor authentication turned on. You will also have to enable "Allow less secure apps" in your google settings.

If you are getting any kind of busy traffic w/ lots of people posting comments, you will probably irritate google very quickly by doing this, so you should set up a real notification email provider instead.


This email address will recieve an email every time a comment is posted.


The password for the admin user on the web-based admin panel. If it is not provided then the admin panel will be turned off.



comments.js looks for a DOM element (like a <div>, for example) with the id sqr-comment-container as soon as it loads. This DOM element must also hava a data attribute called data-comments-url.


This property must be set to a URL of the form <COMMENTS_BASE_URL>/api/<DocumentID>

Each time the comments are instantiated on a page, the script needs to know a unique id associated with the page it is on. This way, if the URL of the page changes, the existing comments will not be lost. This is the DocumentID.

I developed SequentialRead comments for use with the Ghost blogging software. Ghost provides a property called id on every post, so all I had to do was edit the Handlebars template for my Ghost theme and insert that id property into the url on my data-comments-url attribute like so:

<section class="comment">
      class="container limit-width"
    <script src=""></script>

And that's it, that's really all you have to do to include the comments section on your page.

If you wish to use the path part of the URL as the DocumentID, you will have to get clever with a bit of JavaScript, something like this:

<div id="sqr-comment-container"></div>
    // Set the documentId based on the URL and ensure it does not contain any slashes.
    var documentId = window.location.pathname.substring(1).replaceAll("/", "_");

    // Set the data-comments-url property based on the generated documentId
    document.getElementById("sqr-comment-container").dataset.commentsUrl = ""+documentId;
<script src=""></script>

So for example, for the page

The DocumentID would be myProduct_blog_check-out-our-new-blog-commetns-system. If that page was moved to a different URL, for example because someone mis-spelled commetns in the URL, then the DocumentID would change and the existing comments would be lost.


This section is a stub. See source code for details. You don't need to interact with the HTTP API in depth in order to use this product.

All of the routes under /admin require HTTP Basic Authentication. Username will be admin and password will be whatever you set for COMMENTS_ADMIN_PASSWORD.

GET /api/<DocumentID>

Get the JSON list of comments for a document.

POST /api/<DocumentID>

Post a new comment.

GET /admin

Display the list of documents that have comments.

GET /admin/<DocumentID>

Display the list of comments for a document

POST /admin/<DocumentID>

Delete a comment.

GET /avatar

Get an avatar image.

GET /disable/<token>

Disable email notifications for a given user for a given post.

GET /unsubscribe/<token>

Disable email notifications for a given user.

GET /static/<filename>

Get a static file like JavaScript, CSS, or the anonymous user avatar.