Email-based (spaced repetition) course creation tool

๐Ÿ—๏ธ Work-in-progress! ๐Ÿ—๏ธ

The idea is to create a reusable course generator Val designed with effective, research-backed learning techniques in mind. That includes techniques like spaced repetition (via an interval between email lessons), retrieval practice (quizzing and fill-in-the-blank), elaboration and reflection (writing exercises). The Val(s) will include:

  • Email signup and verification
  • Template to fill in with course content
  • SQLite tables to store students and track progress
  • Cron job to send lessons to students

As the first use case for this generalizable course creator, I plan to make a course about Make It Stick, which is a practical book about learning research that gave me the idea.

I'm writing more about the implementation on my digital garden, which is also where you'll find the signup form.

Readme
Fork
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import { fetchConfirmationHtml } from "https://esm.town/v/petermillspaugh/fetchConfirmationHtml";
import { fetchSignupHtml } from "https://esm.town/v/petermillspaugh/fetchSignupHtml";
import { markLessonComplete } from "https://esm.town/v/petermillspaugh/markLessonComplete";
import { refreshToken } from "https://esm.town/v/petermillspaugh/refreshToken";
import { sendLesson } from "https://esm.town/v/petermillspaugh/sendLesson";
import { sendLessonResponses } from "https://esm.town/v/petermillspaugh/sendLessonResponses";
import { sendVerification } from "https://esm.town/v/petermillspaugh/sendVerification";
import { upsertStudent } from "https://esm.town/v/petermillspaugh/upsertStudent";
import { email as sendEmail } from "https://esm.town/v/std/email?v=11";
import { sqlite } from "https://esm.town/v/std/sqlite?v=4";
import { Hono } from "npm:hono";
import { JSX } from "npm:react";
export async function emailSubscription(req: Request) {
const app = new Hono();
app.get("/", async (c) => {
return c.html(fetchSignupHtml());
});
app.post("/send-verification", async c => {
const formData = await c.req.formData();
const name = formData.get("name");
const email = formData.get("email");
if (typeof name !== "string" || typeof email !== "string") {
return Response.json({ message: "Unexpected missing value for name or email." });
}
const token = crypto.randomUUID();
await upsertStudent({ name, email, token });
// Lack of await is intentional: send optimistic success response, then send email and notify myself async
sendVerification({ emailAddress: email, html: fetchConfirmationHtml({ email, token }) });
return Response.json({ success: true, message: "Sent verification email." });
});
app.put("/confirm-verification", async c => {
const email = c.req.query("email");
const token = c.req.query("token");
const { newToken, didRefresh } = await refreshToken({ email, token });
if (didRefresh) {
// Lack of await is intentional: send optimistic success response, then send email and notify myself async
sendVerification({
emailAddress: email,
html: fetchConfirmationHtml({ email, token: newToken, reVerifying: true }),
});
๐Ÿ‘† This is a val. Vals are TypeScript snippets of code, written in the browser and run on our servers. Create scheduled functions, email yourself, and persist small pieces of data โ€” all from the browser.