Memreda is a private handover inbox for one family sharing care:
voice note or forwarded email → editable handover → private family timeline → daily brief
It keeps family updates from email, voice notes, and written notes. Each handover links to its source. It also shows who will do the next action.
Memreda is not medical advice, an emergency service, a medical record, medication management, surveillance, a general family dashboard, or a chatbot.
voice note ─┐
typed note ─┼─▶ stored source ─▶ editable 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)
Each step above is an API boundary in server.js. The app stores each source without changes. AI drafts the three editable handover fields. It does not diagnose or add details. A person must confirm a handover before it appears in the timeline.
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.
- Every first-time user enters a name. This includes owners and invited contributors. The app uses the saved name in member lists and private tags.
- 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 and schema-validated editable fields: What happened, Worth remembering, and Tomorrow.
- Private membership-checked timeline, edit, soft-delete, and JSON export.
- Basic-auth inbound email endpoint for Postmark-style forwarding. It accepts email from known circle members only. It removes duplicate
MessageIDvalues. It creates an owner-reviewable draft before an email handover enters the timeline. - Daily owner digest through Resend. Each delivery attempt is recorded. The owner can turn the digest on or off for the circle.
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 private continuity features to saved handovers:
- What changed? shows family-reported New, Continuing, Resolved, and Needs follow-up items since a member last opened the view. Each item opens its source handover. It does not diagnose or make clinical conclusions.
- Related handovers form topic threads. Each thread shows its first mention, latest update, source count, and evidence.
- The Tomorrow field can create a carry-forward commitment. Members can claim and complete it. They can also add it to the shared action list.
- The action list has manual and commitment-derived items. Members can claim and complete items. It has no recurring tasks, calendar, notifications, projects, or priorities.
- Private-history questions search only the circle's handovers and source text. The answer includes linked evidence. This feature is not a general chat assistant.
- Voice notes, written notes, and forwarded emails have circle-only replies and seen acknowledgements. There are no direct messages, public reactions, or nested threads.
- Members can add or remove private tags on sources, replies, and tasks. Tags do not change the original source. They can appear in the next digest without push notifications.
- When a saved source names a circle member, the app adds a private source tag for that member. It matches the first word of a saved name. A member can remove the tag. The app does not match members who have no saved name.
- The Today view greets the signed-in member by name. Settings lets a member edit their name and view the circle roster.
Continuity extraction is deterministic and source-linked. It remains available when external AI is unavailable. It stores generated 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, the UI shows one-time development links. Without an OpenAI key, uploads use a labeled local-development transcript. Use this mode for flow testing only.
| 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 the SQLite database and all Phase 1 and Phase 2 tables. SQLite WAL mode is enabled. Do not serve the database or the upload directory as public content. Back them up together, and take the database snapshot with VACUUM INTO rather than copying the file: in WAL mode a plain copy opens cleanly, passes PRAGMA integrity_check, and silently omits everything committed since the last checkpoint.
Magic links expire after one hour. Invitation links expire after seven days. Every first-time user enters their name after they use a magic link. This applies to owners and invited contributors. An owner then enters the circle name. Session cookies are HTTP-only and same-site. They are secure when APP_URL uses HTTPS. Timeline, upload, update, delete, and export operations check circle membership. Only an owner can invite members. Inbound email uses constant-time basic-auth comparison. It rejects unknown senders and removes duplicate MessageID values.
Use HTTPS and a strong operational secret. Use private encrypted storage and backups. Validate sender domains before you handle sensitive family material in production.
There are two separate operations, and only one of them is recoverable.
- Remove a handover from the timeline.
DELETE /api/handovers/:idsetsdeleted_at. The handover leaves every view. The source, its transcript, and its audio remain stored, so an owner can still find what was said. - Erase a source permanently.
DELETE /api/sources/:idis owner-only and irreversible. It removes the source and everything derived from it — transcript, note text, handover, pending draft, replies, derived tasks, and tags — inside one transaction, then unlinks the audio file from disk. Topic threads left without evidence are pruned and the remaining source counts are recomputed. If the audio file cannot be unlinked, the row is still gone and the orphaned path is logged for an operator to clear.
Erasure is the operation to use when a family asks for something to be gone. Because it deletes the recording, it removes material that the daily digest and any earlier export may already have carried elsewhere; those copies are outside the app's reach. A backup archive taken before an erasure still contains the erased source, so the backup retention window is the effective ceiling on how long erased material can survive.
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 function in server.js. The prompt applies these limits to every call. If a provider fails, the app uses a labeled fallback.
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 };
}
}The model receives only the selected transcript. validateFields limits every returned string on the server. Timeouts, non-200 responses, and malformed JSON use the same deterministic, labeled fallback. The app does not show 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 FromFull.Email, MessageID, Subject, and TextBody. It can also use stripped HtmlBody. It preserves the source and transcript, then creates an owner-reviewable email draft. Only the owner can publish that draft to the timeline.
Run the digest once with:
npm run digestSchedule this command once each day with a host scheduler, such as cron. When enabled by the owner, it sends a Resend email with handovers from the last 24 hours. It also includes open commitments, pending sources, and new tags. The app records each result in digest_deliveries.
npm testThe tests cover sign-in, name setup for owners and contributors, circle creation, invitations, source access, automatic tags, notes, continuity, commitments, tasks, replies, tags, history search, export, inbound-email deduplication, and outsider denial. Run them where a temporary localhost listener is permitted.
This deployment uses one Node process and a private persistent volume for SQLite and uploads. Deployment files are in deploy/. The service listens only on loopback port 9020. Caddy provides HTTPS. SQLite and audio are stored under /var/lib/memory-lane. Do not place them in a static Caddy root.
A production host needs a public HTTPS hostname, APP_URL, encrypted persistent storage, environment secrets, Resend, OpenAI, and a verified inbound-email provider. Use npm start to start the app, npm run migrate to migrate the database, and npm run digest to send the daily digest.
Memreda has a public landing page at https://memreda.xyz. The private app is at https://app.memreda.xyz. The Node process listens on 127.0.0.1:9020 behind Caddy HTTPS. The former Tailnet /memory-lane preview is not part of this deployment. Verify the live app with curl -fsS https://app.memreda.xyz/api/session. Then complete the two-person flow below before you treat Phase 1 as complete.
- Owner requests a magic link at
https://app.memreda.xyzand receives it at the configured real inbox. - Owner enters their name, creates a circle, and sends an invitation to the contributor's real inbox.
- Contributor accepts the invite, enters their name, 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 deterministic summaries, not clinical conclusions. Review the source handover. The product does not diagnose or certify that a family action occurred.
- Voice notes remain private, replayable, and retryable when a transcription provider is unavailable. Use the
TRANSCRIPTION_*settings to configure an OpenAI-compatible fallback provider.
Memreda was built in Codex with GPT-5.6.
- Product and architecture. GPT-5.6 reviewed the static prototype and designed a single Node process, SQLite in WAL mode, and a private upload volume.
- Implementation. Codex sessions implemented authentication, source capture, handover drafting, continuity features, the daily digest, and deployment files.
- Testing. Codex sessions added the customer-zero and hardening tests.
- Primary Codex session (2026-07-18):
019f76c6-3420-7032-be7b-b1c9df2824d4. An earlier session covered product ideation:019f7695-93cf-70f2-a369-09934d9ee004.
The deployed app makes two types of model call: audio transcription and handover drafting. The models do not diagnose, give advice, or add details. The default models are OpenAI gpt-4o-mini-transcribe and gpt-4.1-mini. Use TRANSCRIPTION_* and HANDOVER_* settings to configure compatible fallback providers.




