N

Nexa BaaS User Guide

Based on In development (pre-release) Last updated 2026-10-12

About This Guide

Caution Nexa BaaS has not been publicly released yet. The repository is private, and there are no downloads, npm packages, or hosted service. This guide outlines the intended workflow based on the features implemented so far; API paths, setting names, and procedures may change at public release.

Nexa BaaS is a self-hosted Backend-as-a-Service for mobile and web app developers. It provides, in a single server, the authentication, permissions, data storage, file uploads, scheduled push notifications, and offline sync that you would otherwise rebuild for every app, and you run it yourself on Docker.

This guide is written for:

  • App developers building on Nexa BaaS — how to use the console, projects, SDK, and REST API
  • Operators who want to run the server themselves — the self-hosting procedure and environment variables

For a product overview, see the app introduction page.

Concepts and Architecture

Key Concepts

TermMeaning
Developer accountThe account you use to sign in to the developer console. Internally, it is a user of the reserved platform tenant
ProjectAn isolated backend space (= tenant) for a single app. One developer account can own several
Project ID (tenantId)The UUID that identifies a project. Used in SDK and API calls
App userAn end user who signed up to a given project. Each project has its own user pool (the same email is a separate account in each project)
CollectionA set of records (JSON) in the Data API. Declares access rules, whether sync is enabled, and unique constraints
BucketA set of files in file storage. Uses access rules with the same syntax as collections

Data in different projects is isolated at the database level by PostgreSQL Row Level Security (RLS), and every request runs inside a transaction with that project’s context set.

Services (12)

ServiceWhat it providesMain access point
AuthenticationEmail-verified sign-up (3 approval modes), sign-in and token refresh, password reset, MFA (TOTP), Google sign-inSDK, REST
Users, groups, permissions (RBAC)User management, groups and memberships, resource:action permissions (deny-first evaluation)Console, admin API
Data APIJSON record CRUD, filtering, sorting, paging, declarative access rules, unique constraintsSDK, console
File storagePresigned direct uploads, large resumable multipart uploads, signed download URLsSDK
Reminders (scheduled push)Device registration, scheduling and cancelling pushes at a chosen timeSDK
Offline syncRevision-based delta pull and batch push, 3 conflict policiesSDK
WebhooksEvent outbox publishing, HMAC-signed delivery with retries, Slack, Telegram, and KakaoWork connectorsConsole, admin API
Developer consoleProject creation; screens for users, data, webhooks, OAuth, logs, and usageWeb
Plan limitsAutomatic blocking at per-plan user-count and storage limitsAutomatic on the server
Usage meteringDaily aggregation of data, file, and sync callsConsole
Log managementPer-category audit log settings and queriesConsole, admin API
Per-project OAuth (BYO)Registration of developer-owned Google credentials (stored encrypted)Console

Components

ComponentDescriptionDefault port
API serverNestJS application. All REST API paths use the /api/v1 prefix (except health checks)3000
Developer consoleVite + React web app5174
Example app Nexa WordsAn offline-first vocabulary app built with the SDK (for reference)5173
PostgreSQLPrimary database (required)5432
Redis (Valkey)Job queue for scheduled push, webhook delivery, and maintenance jobs (optional)6379
S3-compatible object storageFile storage (optional, e.g. MinIO)9000

Quick Start

The overall flow is:

  1. Start the server — see Self-Hosting.
  2. Create a developer account and sign in to the console.
  3. Create a project in the console and note its Project ID.
  4. Create a collection and its access rules on the console’s Data screen.
  5. In your app, give the SDK the server address and project ID, sign up and sign in an app user, then read and write data.
  6. Use the console to approve or block sign-ups and to check logs and usage.

Developer Console

Creating a Developer Account

The console does not have a sign-up form yet. A developer account is created by going through the regular sign-up flow against the platform tenant. The platform tenant ID is set with the server environment variable PLATFORM_TENANT_ID; if unset, the reserved UUID 00000000-0000-4000-8000-000000000000 is used.

# ① Request sign-up — sends a verification email (always responds 202 and never reveals whether the account exists)
curl -X POST http://localhost:3000/api/v1/auth/signup/request \
  -H 'content-type: application/json' \
  -H 'x-tenant-id: <platform tenant ID>' \
  -d '{"email":"dev@example.com"}'

# ② Verify with the token from the email link → receive signupSessionToken
curl -X POST http://localhost:3000/api/v1/auth/signup/verify \
  -H 'content-type: application/json' -H 'x-tenant-id: <platform tenant ID>' \
  -d '{"token":"<token from the email>"}'

# ③ Set a password to complete sign-up
curl -X POST http://localhost:3000/api/v1/auth/signup/complete \
  -H 'content-type: application/json' -H 'x-tenant-id: <platform tenant ID>' \
  -d '{"signupSessionToken":"<value from ②>","password":"<password>","passwordConfirm":"<password>"}'

Note If the mail setting (MAIL_PROVIDER) is left at its default fake, no real email is sent; instead, the verification link is printed to the API server’s console log. When testing locally, get the token from the log.

Signing In and Screen Layout

Sign in with your developer account in the console (default http://localhost:5174). The API address the console connects to and the platform tenant ID can be changed with the console build environment variables VITE_NEXA_BASE_URL (default http://localhost:3000) and VITE_NEXA_PLATFORM_TENANT_ID.

  • Left sidebar — Dashboard and Projects; once a project is selected, also Data, Files, Reminders, Webhooks, Social Login, Usage, Logs, and Users
  • Top bar — the current project, the developer’s email, and sign-out
  • Dashboard — feature catalog cards (what you can do and whether the console supports it). With a project selected, you can open each feature for that project.

Creating a Project and the Project ID

  1. On the Projects screen, enter a name (1–100 characters) and click + Create.
  2. Select a project in the list; all subsequent screens then work on that project.
  3. The project’s ID (UUID) is the SDK’s tenantId and the API’s X-Tenant-Id value.

Note Per-project API keys are not issued at this time. Apps (clients) call the API with the project ID and the app user’s sign-in token (JWT), and administrative tasks are done in the console (with a developer token). In the pre-release design, an API key scheme is left as follow-up work. Deleting or transferring projects is not available yet either.

Users Screen

View and manage the app users who signed up to the project, by status.

  • Approve / Reject — handle pending sign-ups in pre-approval mode
  • Block / Unblock — suspend and restore accounts
  • Reset password — force-send a password reset email to the user (no temporary password is created)
  • Delete account — soft delete. After a default 30-day grace period the email is anonymized, so the same email can sign up again

Data Screen

A playground where you can create collections and insert records directly.

  • Collection names consist of the characters [a-z0-9_-].
  • When creating one, you specify access rules (rules), offline sync (sync) with its conflict policy, and unique constraints (uniqueBy).
  • Records are created and deleted by editing JSON. Deleting a collection also deletes all of its records.

Webhooks Screen

Register a webhook by choosing the receiving URL, the events to receive, and the format (generic, slack, telegram, kakaowork). Telegram additionally requires a chatId. In Delivery history below, you can check status and attempt counts and requeue failed deliveries. See Webhooks for details.

Social Login (BYO) Screen

Register Google OAuth credentials dedicated to the project, and the Google consent screen shows the developer’s app name.

  1. Create an OAuth client in the Google Cloud Console.
  2. Register the app name, Client ID, and Client Secret in the console.
  3. Add the callback URI shown in the registration response to Authorized redirect URIs in the Google Cloud Console.

Deleting the credentials falls back to the server-wide shared credentials (if configured). Registration requires the credential encryption key (CREDENTIALS_ENCRYPTION_KEY) to be set on the server.

Usage and Logs Screens

  • Usage — view daily aggregates of data, file, and sync calls over 7-, 30-, or 90-day ranges.
  • Logs — turn audit logging on or off per category (auth, admin, user, data, file, reminder, hook) and query by period and user. auth and admin are for security auditing and cannot be turned off.

Files and Reminders Screens

These two features can only be called with an app user token, so the console shows guidance only. Test them with the SDK or the REST API.

Client SDK

Installation and Initialization

@nexa/sdk is a TypeScript client that runs on the web, React Native, and Node, with no runtime dependencies (it uses only fetch and crypto).

Caution The SDK has not been published to npm yet. For now it is available only as a workspace package inside the repository; npm publication is deferred until the service goes public.

import { createClient } from '@nexa/sdk';

const nexa = createClient({
  baseUrl: 'https://api.example.com', // the SDK adds the '/api/v1' prefix
  tenantId: '<project ID>',
  // store: if omitted, stored in memory (lost when the app exits)
  // syncIntervalMs: 30_000,          // auto-sync interval for sync collections (optional)
});

Sign-In and Sessions

await nexa.auth.login('user@example.com', 'password');
nexa.auth.onAuthLost(() => {
  // the server rejected the session — send the user to the sign-in screen
});
await nexa.auth.logout();
  • Tokens are saved in the configured store and restored when the app restarts.
  • When the API returns 401, the SDK refreshes the token once and resends the original request.
  • A refresh that fails because the network is down is not treated as a lost session. onAuthLost is called only when the server rejects the session.
  • App user sign-up is done through the REST API, not an SDK method (see Authentication).

Caution For accounts with MFA enabled, the SDK’s login() throws an AUTH_MFA_REQUIRED error. The SDK does not currently support the MFA sign-in step.

Online Collections

const memos = nexa.collection<{ title: string; done?: boolean }>('memos');

const created = await memos.create({ title: 'First memo' });
await memos.update(created.id, { done: true }); // merged per top-level key
const page = await memos.list({
  filter: { done: true, 'meta.count': { gte: 3 }, tag: { in: ['a', 'b'] } },
  sort: '-updated_at',
  page: 1,
  pageSize: 20,
});
await memos.remove(created.id);

Every call goes straight to the server. On failure, a NexaError(code, message, status) carrying the server error code is thrown.

Offline-First (sync) Collections

The collection must be created on the server with sync: true.

const words = nexa.collection<{ term: string; memo?: string }>('words', {
  sync: true,
  onConflict: (r) => console.log('Item merged/rejected by the server', r.id, r.error),
});

const w = await words.put({ term: 'apple' }); // saved locally right away; id is an auto-generated UUID
const items = await words.all();               // local snapshot
const off = words.subscribe((items) => render(items));
const { pushed, pulled, resetOccurred } = await words.sync(); // push my changes → pull others' changes
  • All reads and writes are handled locally and immediately, so they work the same offline.
  • Editing the same record several times collapses into a single pending change.
  • Conflicts follow the result of the server’s conflict policy as-is. Merged or rejected items are replaced with the server’s value and reported through onConflict.
  • Sync is triggered in three ways: manual sync(), the syncIntervalMs interval, and the browser online event. Call nexa.dispose() when the app shuts down.

Persistent Stores

The default memory store is lost when the process ends. To keep sign-in state and offline data across app restarts, inject a store.

// Web — IndexedDB
import { createClient, IndexedDBLocalStore } from '@nexa/sdk';
const nexa = createClient({ baseUrl, tenantId, store: new IndexedDBLocalStore() });
// React Native / Node — wrap any SQLite library in 2 methods and inject it
import { createClient, SqlLocalStore, type ISqlDriver } from '@nexa/sdk';
import * as SQLite from 'expo-sqlite';

const db = await SQLite.openDatabaseAsync('nexa.db');
const driver: ISqlDriver = {
  run: async (sql, params) => void (await db.runAsync(sql, ...(params as []))),
  all: async (sql, params) => db.getAllAsync(sql, ...(params as [])),
};
const nexa = createClient({ baseUrl, tenantId, store: new SqlLocalStore(driver) });

File and Reminder Helpers

// Files — direct upload that bypasses the server. Switches to multipart automatically above 32MiB
const docs = nexa.files('docs');
const file = await docs.upload(blob, { name: 'note.txt' });
window.open(file.downloadUrl); // signed URL valid for 5 minutes
await docs.list();
await docs.remove(file.id);

// Register a device + schedule a reminder
await nexa.devices.register(fcmToken, 'fcm');
const r = await nexa.reminders.schedule({
  sendAt: new Date(Date.now() + 60_000),
  payload: { title: 'Time to review' },
});
await nexa.reminders.cancel(r.id);

Using Each Service

Common Rules

  • Path prefix: /api/v1 (except the health checks /health and /health/ready)
  • Project selection: the token’s project if a sign-in token is present, otherwise the X-Tenant-Id header
  • Authentication: Authorization: Bearer <access token>
  • Response format: success is { "success": true, "data": … } (with meta for lists), failure is { "success": false, "error": { "code", "message" } }
  • Every response carries an X-Trace-Id header for tracing.

Authentication

FeaturePath
Sign-up request → verify → completePOST /auth/signup/request → POST /auth/signup/verify → POST /auth/signup/complete
Sign-in / MFA second stepPOST /auth/login, POST /auth/login/mfa
Token refresh / sign-outPOST /auth/refresh, POST /auth/logout
Password resetPOST /auth/password/reset/request → /verify → /complete
My profileGET·PATCH /me, POST /me/password, POST /me/password/set
MFA (TOTP)POST /me/mfa/totp/enroll → POST /me/mfa/totp/activate, DELETE /me/mfa/totp
Google sign-inGET /auth/oauth/… (requires server-wide or per-project Google credentials)
  • Sign-up verifies the email first, so no account is created until the link in the email is confirmed. Verification links are valid for 10 minutes by default.
  • There are three approval modes, set per project: immediate full access (AUTO_FULL), basic access with some permissions requiring approval (AUTO_BASIC), and admin pre-approval (PRE_APPROVAL).
  • Passwords go through a strength check and are checked against a list of breached passwords.
  • Access tokens last 15 minutes and refresh tokens 14 days by default (configurable on the server). Refresh tokens are rotated on every use, and if reuse is detected, all sessions are invalidated.
  • A Google account is automatically linked to an existing account with the same email only if the email is verified.

Users, Groups, and Permissions

The admin API is called with a console developer token (Authorization + the target project’s X-Tenant-Id).

  • Users: /admin/users — list, details, update; approve, block, unblock, delete; password reset email
  • Groups: /admin/groups — CRUD, POST /admin/groups/:id/members, DELETE /admin/groups/:id/members/:userId
  • Permissions: /admin/permissions (definitions), group grants, per-user allow/deny overrides
  • Permission keys use the resource:action format, evaluated in the order user deny > user allow > group grant > default deny.

Data API

Creating a collection (admin API or the console Data screen):

curl -X POST http://localhost:3000/api/v1/admin/collections \
  -H 'authorization: Bearer <developer token>' -H 'x-tenant-id: <project ID>' \
  -H 'content-type: application/json' \
  -d '{"name":"memos","rules":{"read":["owner"],"create":["authenticated"],"update":["owner"],"delete":["owner"]}}'

Access rules have four kinds of principals.

PrincipalMeaning
publicAlso allows requests without sign-in (for public reads)
authenticatedA signed-in app user
ownerThe user who created the record. List queries are automatically filtered to the user’s own records
permission:<key>Users who have the RBAC permission
  • Actions are read, create, update, and delete; any action not listed in the rules is denied.
  • Record API: POST·GET /data/:collection, GET·PATCH·DELETE /data/:collection/:id
  • List queries: filter[field][op]=value (operators eq, ne, gt, gte, lt, lte, in, like; dot-notation paths allowed), sort=-updated_at, page and pageSize (default 20, max 100)
  • Writes always require sign-in. Calls without sign-in need the X-Tenant-Id header and only public reads are allowed.
  • You can prevent duplicates on a combination of 1–3 top-level fields, e.g. uniqueBy: ["term"] (violations return DATA_006).
  • Limits: 64KB per record, 100 collections per project, 10 filters. Deleting a record is immediate and permanent.

File Storage

  • Bucket management: /admin/file-buckets — rules are read (download), create (upload), and delete
  • Upload flow: POST /files/:bucket (prepare upload; issues an upload URL valid for 15 minutes) → the client PUTs directly to storage → POST /files/:bucket/:id/complete (size is checked, then the upload is finalized)
  • Retrieval: GET /files/:bucket/:id (includes a download URL valid for 5 minutes), GET /files/:bucket (list), DELETE /files/:bucket/:id
  • Large files: multipart: true in the prepare request (up to 5GiB, 8MiB parts). Requesting part URLs again lets you resume, and abort cancels.
  • Single uploads are limited to 32MiB. Uploads not completed within 24 hours are cleaned up automatically.

Reminders (Scheduled Push)

  • Devices: POST /me/devices (the same token is updated), GET /me/devices, DELETE /me/devices/:id
  • Scheduling: POST /reminders { sendAt, payload: { title, body, data } } — sendAt must be between 30 seconds and 1 year from now
  • Query and cancel: GET /reminders?status=, DELETE /reminders/:id (only while scheduled)
  • Limits: 100 characters for the title, 500 for the body, 4KB for data, and 500 pending reminders per user.
  • Redis is required for reminders to actually be sent. Real push delivery requires FCM configuration (PUSH_PROVIDER=fcm); the default is fake, which only writes to the server log.

Offline Sync

  • Create the collection with sync: true and choose a conflict policy (conflictPolicy).
PolicyBehavior
lww (default)The later write wins
fieldMergeMerges per top-level field
serverWinsRejects the conflicting write and returns the server value
  • Pull: GET /sync/:collection?since=<seq>&limit= (default 200, max 1000)
  • Push: POST /sync/:collection/push (up to 200 items at a time, partial success per item) — record ids are client-generated UUID v4s
  • Deletions propagate as markers (tombstones) and are cleaned up after 90 days. Pulling from a position that is too old returns resetRequired: true, and everything is pulled again (handled automatically by the SDK).
  • Sync requires sign-in; with the SDK you never need to handle the protocol above directly.

Webhooks

  • Registration: /admin/webhooks (10 per project). The signing secret is shown only once at registration, so store it right away.
  • Events: record.created, record.updated, record.deleted, user.registered, file.uploaded, file.deleted, reminder.sent
  • The generic format is sent with an X-Nexa-Signature: sha256=<HMAC-SHA256 of the body> signature, plus X-Nexa-Event and an X-Nexa-Delivery header for deduplication. Verify the signature on the receiving server.
  • On failure, deliveries are retried at intervals of 1 minute → 5 minutes → 30 minutes → 2 hours → 12 hours, and are marked as failed after more than 5 attempts. History: GET /admin/webhooks/events; resend: POST /admin/webhooks/events/:id/retry.
  • Delivery is handled by a Redis-based worker roughly every minute. Without Redis, deliveries stay pending.

Plan Limits, Usage, and Logs

  • Per-plan limits (the server currently enforces only the user count and storage capacity):
PlanUsersStorage
free1001GiB
starter1,00010GiB
growth10,000100GiB
enterpriseUnlimitedUnlimited
  • Usage: GET /console/projects/:tenantId/usage?from=&to= (default: last 30 days)
  • Logs: GET /admin/logs; settings via GET·PUT /admin/logs/settings
  • Major APIs are rate-limited to about 120 requests per minute (per project × user); exceeding it returns CMN_004.

Common Error Codes

CodeMeaning
AUTH_002Email not verified, or token invalid or expired
AUTH_003Invalid credentials
USER_002Permission denied (access rule or RBAC denial)
DATA_001 / DATA_005Collection not found / record not found
DATA_004Record size exceeded (64KB)
DATA_006Unique constraint violation
FILE_004 / FILE_005File size exceeded / invalid upload state
RMD_002 / RMD_003Send time out of range / cannot be cancelled in this state
SYNC_001Sync is not enabled for the collection
CON_001 / CON_002Not a console account / project not found or not owned
CMN_002 / CMN_004Limit exceeded / too many requests

Self-Hosting

Caution The procedure below is the development and verification setup documented in the repository. There is no production deployment guide yet (TLS, backups, scaling, etc.), and until the repository is made public you need repository access to follow it.

Prerequisites

LevelWhat you needWhat you can do
MinimalNode.js 22, pnpm (corepack)Build and test, start the server and health checks
Basic+ PostgreSQL connection detailsUse most features via the console and SDK
Full+ Docker (PostgreSQL, Redis, MinIO)Queue features such as scheduled push and webhook delivery
Production+ mail, FCM, and Google credentialsReal email, push, and social sign-in

Installation and Startup

corepack enable pnpm                       # once, the first time
pnpm install

docker compose up -d postgres redis minio  # local infrastructure
cp apps/api/.env.example apps/api/.env     # prepare environment variables, then fill in values

pnpm --filter @nexa/shared build           # once, the first time
pnpm --filter @nexa/api migrate            # apply DB migrations
pnpm dev                                   # run API, console, and example app together
  • Run individually: pnpm --filter @nexa/api dev (API :3000), pnpm --filter nexa-console dev (console :5174), pnpm --filter example-words dev (example app :5173)
  • Health checks: GET /health (liveness), GET /health/ready (checks the DB connection)
  • To run the API in a container as well, use docker compose --profile full up -d.
  • To use the breached-password list, load it beforehand with pnpm --filter @nexa/api seed:breached.

Key Environment Variables

VariableDescription
DATABASE_URLPostgreSQL connection string (required for DB features)
JWT_SECRETToken signing key (16+ characters, required in production)
ACCESS_TOKEN_TTL_SECONDS / REFRESH_TOKEN_TTL_SECONDSToken lifetimes (default 900 seconds / 14 days)
APP_BASE_URLBase address used for links in authentication emails
MAIL_PROVIDER · MAIL_FROMMail delivery method (fake by default, or resend) and sender address
REDIS_URLQueue features (scheduled push, webhooks, maintenance jobs)
STORAGE_ENDPOINT · STORAGE_ACCESS_KEY · STORAGE_SECRET_KEY · STORAGE_BUCKET · STORAGE_REGIONS3-compatible object storage
PUSH_PROVIDER · FCM_SERVICE_ACCOUNT_JSONPush delivery method (fake by default, or fcm) and FCM service account (base64)
GOOGLE_CLIENT_ID · GOOGLE_CLIENT_SECRETServer-wide Google sign-in (disabled if unset)
ADMIN_API_KEYAdmin API key for operators (16+ characters, X-Admin-Key header)
PLATFORM_TENANT_IDPlatform tenant ID for developer accounts

Caution .env.example lists the storage settings under MINIO_* names, but the names the server actually reads are the STORAGE_* ones in the table above. Use STORAGE_* to enable file features.

Caution Never commit secrets such as keys and passwords to the repository; manage them only through environment variables or Docker secrets. In addition, set MFA_MASTER_KEY for encrypting MFA secrets (recommended) and CREDENTIALS_ENCRYPTION_KEY (32-byte base64) for encrypting BYO OAuth credentials.

Version History

Version Flow Summary

There is no official version number yet (package version 0.0.0). Below are the development milestones by date.

DateMilestone
2026-07-28Comprehensive run and test guide; documented what is and isn’t implemented
2026-07-16Integration testing against real DB and Redis containers
2026-07-15Developer console navigation screens completed (dashboard, data, webhooks, OAuth, usage)
2026-07-14Webhooks and connectors, plan limits, usage metering, BYO OAuth, log management
2026-07-13Developer console and projects, example app Nexa Words, unique constraints
2026-07-12Data API, file storage, reminders, offline sync, SDK
2026-07-11Sign-in and tokens, MFA, Google sign-in, users, groups, RBAC
2026-07-10Email-verified sign-up
2026-06-23Project started (monorepo and server skeleton)

2026-07-28 — Documentation

Added

  • Comprehensive run and test guide by setup level
  • List of what is and isn’t implemented, per feature

2026-07-16 — Integration Tests

Added

  • Integration tests and CI jobs that verify migrations, tenant isolation, transactions, and queue round-trips against real PostgreSQL and Redis containers

2026-07-15 — Console Navigation

Added

  • Console sidebar, project switching, and feature catalog dashboard
  • Data playground (collection and record CRUD)
  • Webhook registration, delivery history, and resend; BYO OAuth credentials screen
  • Usage dashboard (7, 30, 90 days)

Improved

  • Responsive layout and accessibility cleanup

2026-07-14 — Operational Features

Added

  • Webhook outbox publishing, HMAC-signed delivery and retries, file/reminder/sync events, delivery history and resend
  • Slack, Telegram, and KakaoWork connectors
  • Automatic blocking at plan limits (user count, storage)
  • Daily usage aggregation and console view
  • Per-project Google OAuth credentials (stored encrypted)
  • Per-category audit log settings and queries
  • SDK file and reminder helpers, automatic multipart switching for large files

Fixed

  • Off-by-one-day dates in usage queries

2026-07-13 — Developer Console

Added

  • Developer accounts (platform tenant) and project creation, list, and details
  • Admin API access with console developer tokens; admin-triggered password reset email
  • Console web UI (sign-in, projects, users, logs)
  • Example app Nexa Words (offline-first vocabulary app)
  • Collection unique constraints (uniqueBy)

Fixed

  • SDK defects in browser environments

2026-07-12 — Data, Files, Sync, SDK

Added

  • Data API (declarative access rules, queries, RBAC integration)
  • File storage (presigned uploads, resumable multipart, list, delete)
  • Reminders (device registration, scheduling, delivery worker, recovery)
  • Offline sync (delta pull, batch push, 3 conflict policies, tombstone cleanup)
  • Client SDK (@nexa/sdk) with IndexedDB and SQLite persistent stores
  • Anonymization of deleted accounts after a 30-day grace period

2026-07-11 — Authentication Track Completed

Added

  • Sign-in, token issuance and refresh (rotation and reuse detection), sign-out
  • Password reset and change, admin approval
  • Google social sign-up, linking, and sign-in
  • /me settings and MFA (TOTP)
  • Admin user management, groups and memberships, RBAC

Changed

  • All timestamps unified to UTC; dedicated DB schema introduced

2026-07-10 — Sign-Up

Added

  • Email-first verified sign-up flow, password strength and breach checks, 3 approval modes

2026-06-23 — Project Start

Added

  • pnpm + Turborepo monorepo, NestJS server skeleton, Docker Compose development infrastructure
  • Common response format, error codes, logging, DB connection and migrations (06-23 to 06-26)

Known Limitations

  • Not publicly released — there are no downloads, hosted service, or npm package, and the repository is private.
  • No per-project API keys — clients call only with the project ID and user tokens. Project deletion/transfer and team member invitations are not available yet either.
  • No sign-up screen in the console — developer accounts must be created through the sign-up API.
  • SDK — does not support MFA sign-in or a sign-up method.
  • Files and Reminders screens in the console — these are app-user-only features and cannot be tested from the console.
  • Social sign-in supports Google only.
  • Billing and subscription charging not implemented — plan limits block only on user count and storage capacity.
  • No real-time features — sync is periodic or manual; there are no WebSocket real-time subscriptions or GraphQL.
  • Webhooks are outbound only (no receiving webhooks from external services; secret rotation is planned for later).
  • Rate limits are counted per server process, so running multiple servers effectively raises the limit.
  • /health/ready checks only the DB, not Redis or storage.
  • Unless MAIL_PROVIDER is set to resend, emails are not actually sent even in production mode.
  • Databases other than PostgreSQL do not work yet.
  • End-to-end verification requiring real credentials (FCM push, real webhook delivery, per-project Google consent screen) and legal review of the terms and privacy policy are still pending.

↑ Back to top