Memreda is a low-friction handover inbox for one family sharing care:
voice note or forwarded email β editable handover β private family timeline β daily brief
It preserves continuity from messy channels people already use, with source-backed handovers and clear ownership of what must carry forward. It is intentionally not medical advice, an emergency service, medical records, medication management, surveillance, a generic family dashboard, or a chatbot.
voice note ββ
typed note ββΌββΆ immutable source ββΆ cautious AI handover ββΆ private timeline ββΆ daily brief
forwarded β (audio/text kept (edit before it (source-linked (Resend,
email β verbatim, never ever counts as continuity: every send
overwritten) saved) what changed, logged)
carry-forward,
tags, replies)
Every arrow above is a real API boundary in server.js, not aspirational: a source is written once and never mutated, the AI call only ever drafts the three editable handover fields (never a diagnosis, never invented detail), and nothing reaches the shared timeline without a human confirming it first.
Start a circle β share an update β review the editable handover β the private timeline. Live screenshots from a fresh instance, not mockups.
This Phase 1 app supports one private family circle per signed-in owner in the UI:
- Passwordless owner sign-in using expiring email magic links.
- A private circle and email/link invitation for contributors.
- Browser-recorded or uploaded audio voice notes.
- Immutable stored source metadata and transcript, separately from AI output and later edits.
- OpenAI transcription plus cautious, schema-validated editable fields: What happened, Worth remembering, and Tomorrow.
- Private membership-checked timeline, edit, soft-delete, and JSON export.
- Validated basic-auth inbound email endpoint for Postmark-style forwarding. It only accepts known circle members, deduplicates by MessageID, and creates an owner-reviewable draft before any email-derived handover can enter the timeline.
- Daily owner digest script via Resend, with every delivery attempt recorded.
The incoming-email and digest transport patterns were informed by /opt/verasettle-repos/verasettle-email-agent; no payout, settlement, finance, or product logic was copied.
Phase 2 adds a deliberately narrow private continuity layer on top of saved handovers:
- What changed? presents conservative family-reported New, Continuing, Resolved, and Needs follow-up items since a member last opened that view. Every item opens its source-backed handover; it does not diagnose or make clinical conclusions.
- Related handovers gather into lightweight topic threads showing first mention, latest update, source count, and handover evidence.
- A handover's explicit Tomorrow wording becomes a lightweight carry-forward commitment. Circle members can claim and complete it, and can optionally place it on the small manual/shared action list.
- The circle action list supports only manual or commitment-derived items, claim, completion, provenance, and timestamps. It has no recurring tasks, calendar, notifications, projects, or priorities.
- A constrained private-history question searches only the circle's saved handovers and original private source text, then returns a short answer and linked evidence. It is not a general chat assistant.
- Each voice note, written note, and forwarded email has circle-only chronological replies and a seen acknowledgement. There are no DMs, global chat rooms, public reactions, or nested threads.
- Members can add or remove private post-hoc tags on sources, replies, and tasks. Tags are annotations; they never alter the original source and can be included in the next digest without creating push notifications.
Continuity extraction is currently deterministic and source-linked, so it remains available while external AI is unavailable. It stores generated continuity records separately from sources and editable handovers.
Requirements: Node 20+ and npm.
cp .env.example .env
npm install
npm run migrate
npm run devOpen http://localhost:3000. Without email credentials, sign-in and invitation responses show one-time development links in the UI. Without an OpenAI key, uploads use a clearly labelled local-development transcript fallback; this is for flow testing only, not a real transcription result.
| Variable | Purpose |
|---|---|
PORT |
HTTP port, default 3000. |
APP_URL |
Public canonical HTTPS URL used in sign-in and invitation links. It must be a new dedicated hostname, not the existing Tailnet /memory-lane path. |
DATABASE_PATH |
SQLite file path, default ./data/memory-lane.db. |
UPLOAD_DIR |
Private filesystem directory for voice-note files, default ./uploads. Do not expose it as static content. |
SESSION_SECRET |
Reserved for deployment secret management; use a long random value. |
OPENAI_API_KEY |
Enables real OpenAI transcription and handover generation. |
OPENAI_TRANSCRIPTION_MODEL |
Defaults to gpt-4o-mini-transcribe. |
OPENAI_HANDOVER_MODEL |
Defaults to gpt-4.1-mini. |
TRANSCRIPTION_API_URL, TRANSCRIPTION_API_KEY, TRANSCRIPTION_MODEL |
Optional OpenAI-compatible transcription override (for example Groq's /openai/v1/audio/transcriptions with whisper-large-v3). Falls back to the OPENAI_* settings when unset. |
HANDOVER_API_URL, HANDOVER_API_KEY, HANDOVER_MODEL |
Optional OpenAI-compatible chat-completions override for handover drafting. Falls back to the OPENAI_* settings when unset. |
RESEND_API_KEY, EMAIL_FROM |
Enable real magic-link, invitation, and digest delivery. |
INBOUND_BASIC_USER, INBOUND_BASIC_PASS |
Required together to enable the inbound email webhook. Use the credentials in the provider webhook URL/auth header. |
npm run migrate creates a SQLite database and the Phase 1 tables plus Phase 2 continuity tables for member visits, topic evidence, change items, commitments, circle tasks, source replies, seen acknowledgements, member tags, and private-history query evidence. SQLite WAL mode is enabled. Back up the database and the private upload directory together; neither is public static content.
Magic links expire after one hour; invitation links expire after seven days. Session cookies are HTTP-only, same-site, and secure when APP_URL is HTTPS. Every timeline, upload, update, delete, and export operation checks circle membership; only the owner can invite. Inbound email uses constant-time basic-auth comparison, rejects unknown senders without revealing membership, and deduplicates MessageID values.
Use HTTPS, a strong operational secret, private encrypted disk/object storage, backups, deletion/retention procedures, and a real sender/domain validation policy before handling sensitive family material in production.
Audio is sent to OpenAI only when OPENAI_API_KEY is configured. The transcription is stored separately. A second OpenAI request drafts only the three editable handover strings from that transcript. Server-side length/type validation is applied before persistence. The prompt instructs the model not to diagnose, give medical advice, invent details, or turn family-reported wording into a conclusion. Users see that output is editable before saving.
This is the actual function (server.js), not a paraphrase β the constraint lives in the prompt sent on every call, and a provider outage degrades to a labelled fallback instead of blocking the family's flow:
async function generateHandover(transcript) {
if (!HANDOVER_KEY) return { ...fieldsFromText(transcript), provider: 'local-development-fallback', model: null };
const prompt = `Turn this private family voice-note or email transcript into a cautious editable handover.
Do not diagnose, give medical advice, infer facts, or add details. Preserve family-reported wording.
Return JSON only with strings: happened, remembering, tomorrow. ...`;
try {
const response = await fetch(HANDOVER_URL, {
method: 'POST',
headers: { authorization: `Bearer ${HANDOVER_KEY}`, 'content-type': 'application/json' },
body: JSON.stringify({
model: HANDOVER_MODEL,
messages: [
{ role: 'system', content: 'You write concise, non-medical, source-faithful family handovers.' },
{ role: 'user', content: prompt },
],
response_format: { type: 'json_object' },
temperature: 0.2,
}),
signal: AbortSignal.timeout(45_000),
});
if (!response.ok) return { ...fieldsFromText(transcript), provider: `${providerName(HANDOVER_URL)}-unavailable-fallback`, model: HANDOVER_MODEL };
const body = await response.json();
return { ...validateFields(JSON.parse(body.choices?.[0]?.message?.content || '{}')), provider: providerName(HANDOVER_URL), model: HANDOVER_MODEL };
} catch {
return { ...fieldsFromText(transcript), provider: `${providerName(HANDOVER_URL)}-unavailable-fallback`, model: HANDOVER_MODEL };
}
}Three things worth noticing: the model never sees or touches anything except the transcript it was called with, validateFields re-clamps every string server-side regardless of what the model returns, and a timeout, a non-200, or a malformed JSON body all fall through to the same deterministic, clearly-labelled fallback rather than surfacing a raw provider error to a family member.
Configure a Postmark-style provider to POST JSON to POST /api/inbound/email with HTTP basic authentication. The endpoint accepts the provider fields FromFull.Email, MessageID, Subject, and TextBody (or stripped HtmlBody), preserves the source/transcript, and creates an owner-reviewable email draft. Only the owner can edit and publish that draft to the family timeline.
Run the digest once with:
npm run digestSchedule that command once per day through the host scheduler (for example a cron job or platform scheduled job). It emails the owner a brief of last-24-hour handovers using Resend and records success/failure in digest_deliveries.
npm testThe integration test exercises owner magic-link sign-in, circle creation, contributor invitation/acceptance, private audio authorization, typed-note source persistence, source-linked continuity, commitments, task claim/completion, source replies/seen acknowledgements, source/reply/task tags, private-history evidence, export, inbound-email deduplication, and outsider denial against a temporary SQLite database. Run it where a temporary localhost listener is permitted.
This is one Node process plus a persistent private volume for SQLite/uploads. Production service, digest timer, Caddy hostname snippet, environment template, and an installation guide are in deploy/. The unit listens only on loopback port 9020; Caddy is the HTTPS edge. SQLite and audio are stored under /var/lib/memory-lane, never in a static Caddy root. A production host must provide: a new public HTTPS hostname, APP_URL, persistent encrypted storage, environment secrets, Resend, OpenAI, and a verified inbound-email provider. Start command: npm start; migration command: npm run migrate; daily scheduled command: npm run digest.
Memory Lane is deployed at https://memreda.xyz. The Node process listens only on 127.0.0.1:9020 behind Caddy HTTPS; the former Tailnet /memory-lane preview still points to the old Python static server and is not used by this deployment. Verify the live app with curl -fsS https://memreda.xyz/api/session, then complete the two-person customer-zero flow below before treating Phase 1 as complete.
- Owner requests a magic link at
https://memreda.xyzand receives it at the configured real inbox. - Owner creates a circle and sends an invitation to the contributor's real inbox.
- Contributor accepts the invite, records a short browser voice note, reviews the OpenAI-generated editable fields, and saves it.
- Owner confirms the handover and preserved transcript in the private timeline, edits it, exports it, and receives the scheduled daily brief.
- Contributor forwards an email through the configured Postmark inbound address; the owner reviews/edits the resulting email draft before publishing it to the timeline.
- Not for emergencies: call local emergency services when someone may be in immediate danger.
- Not medical advice or a clinical record; verify health decisions with appropriate professionals.
- One-circle customer-zero UI; no multi-family administration or complex RBAC.
- Inbound sender validation is secure transport plus membership matching, but production should also enforce provider SPF/DKIM/domain verification.
- Audio files are local private disk storage in this deployment shape; move to encrypted object storage for multi-instance hosting.
- Phase 2 continuity records are conservative deterministic summaries rather than clinical conclusions. Review/edit the underlying handover; the product intentionally does not make diagnoses or certify that a family action occurred.
- Saved voice notes remain private, replayable, and retryable even while a transcription provider is unavailable; the
TRANSCRIPTION_*override exists so an OpenAI-compatible fallback provider can restore the live voice β transcript β handover flow without any code change.
Memreda was built in Codex, with GPT-5.6 doing the reasoning and code generation end to end:
- Product and architecture. GPT-5.6 audited the original static prototype, preserved its warm mobile-first visual direction, and designed the smallest production-grade architecture that could honestly serve one family: a single Node process, SQLite in WAL mode, and a private upload volume β no framework, no ORM, no build step.
- Core implementation. The authentication flow (magic links, sessions, invitations), immutable source capture for voice/typed/email inputs, the transcription β cautious editable-handover pipeline, the Phase 2 continuity layer (What changed?, topic threads, carry-forward commitments, circle tasks, source replies and seen acknowledgements, member tags, evidence-linked private-history questions), the daily owner digest, and the systemd/Caddy deployment files were all implemented through Codex sessions.
- Testing. The end-to-end customer-zero integration test β owner sign-in through invitation, voice and note capture, continuity, commitments, tasks, inbound-email deduplication, outsider denial, and export β was written in Codex alongside the features it covers.
- Primary Codex session (majority of core functionality, 2026-07-18):
019f76c6-3420-7032-be7b-b1c9df2824d4. Product ideation ran in an earlier session the same day (019f7695-93cf-70f2-a369-09934d9ee004).
At runtime the deployed app makes exactly two kinds of model calls, both deliberately constrained: audio transcription, and drafting the three editable handover strings from a transcript (never diagnosis, advice, or invented detail). These default to OpenAI (gpt-4o-mini-transcribe, gpt-4.1-mini) and are OpenAI-compatible-configurable via the TRANSCRIPTION_*/HANDOVER_* variables so a fallback provider can keep a family's flow alive through a quota gap.




