@riposte.co/server
v0.4.6
Published
<p align="center"> <img src="assets/riposte-text.svg" alt="Riposte logo" width="280"> </p>
Downloads
220
Readme
Riposte
On-premise email sync service - Cloud-API alternative
Riposte provides a unified API for multiple email providers (Gmail, Microsoft, future IMAP) with 90% cost savings compared to Cloud-APIs. All data stays on your infrastructure.
🚀 Quick Start
# Scaffold your configuration (riposte/sync.config.json + riposte/.env.example)
npx riposte-init
# Launch the daemon with the admin portal
npx riposte-run
# Skip the admin portal
npx riposte-run --no-adminThat's it! Your email sync service is running at:
- API Server: http://localhost:8080
- Admin Portal: Auto-selected port printed on startup (for example http://localhost:3001)
Installation at a glance
Riposte follows a three-step flow so you always know what comes next:
- Initialize – Run
npx riposte-initto auto-detect local services, scaffoldriposte/sync.config.json/.env.example, and hand off deeper customization to the admin onboarding wizard. - Launch – Start the runtime with
npx riposte-runand watch the structured logs confirm database, queue, and migration health. - Admin – Visit the onboarding flow at the admin portal URL shown after startup. It now behaves like a linear checklist: choose Google or Microsoft first, paste credentials with the exact scopes we flag, wire databases/queues, and (optionally) authorize a pilot account before copying the API call examples into your app.
Each stage is documented in the installation guide, which expands on prerequisites, troubleshooting tips, and day-two operations once you are live.
📦 Installation Options
What ships in the npm tarball?
Running npm publish --dry-run (or npm pack) lists every file that would be
included in the release. You will see a number of entries under
admin-dist/.next/standalone/node_modules/…. Those files come from the
Next.js "standalone" build that powers the bundled admin portal. They are not a
copy of your development node_modules; instead they are the trimmed runtime
dependencies that Next.js inlines alongside server.js so the riposte-admin
CLI can boot without relying on an external installation. The build script
already deletes caches and other transient output, so the additional files are
expected and safe to publish.
Option 1: Global Installation (Recommended)
npm install -g @riposte.co/server
riposte-init
riposte-runOption 2: Local Installation
npm install @riposte.co/server
npx riposte-init
npx riposte-runOption 3: Development Setup
git clone <private-repo>
cd riposte
npm install
npm run setup
npm start⚙️ Configuration
Quick Start (SQLite + In-Memory)
- Database: SQLite file (
./riposte.db) - Cache: In-memory
- Queues: In-memory
- Perfect for: Trying out, development, small deployments
Production Setup (PostgreSQL + Redis)
- Database: PostgreSQL
- Cache: Redis
- Queues: Redis
- Perfect for: Production, horizontal scaling
Database Configuration & Migrations
- Set
DATABASE_URL(orTEST_DATABASE_URL) tofile:./path/to.dbor anysqlite:URL to run with SQLite. - Set the same variable to a PostgreSQL connection string (for example
postgresql://user:pass@localhost:5432/riposte) to run with Postgres. - The service automatically creates SQLite database files when using
file:URLs. - Generate a new migration with
npm run db:generate <migration-name>. The command compares the current schema to your target database and writes the SQL files underdrizzle/sqliteordrizzle/postgresas appropriate. - Run migrations with
npm run db:migrate. The script picks the correct migration directory (drizzle/sqliteordrizzle/postgres) based on the active provider. - Generated migrations live under
drizzle/sqliteordrizzle/postgresdepending on the dialect selected when runningdrizzle-kit. - Riposte now auto-applies pending migrations during startup. Set
AUTO_APPLY_MIGRATIONS=false(orriposte.migrations.autoApplytofalseinsync.config.json) if you prefer to run the migration script manually.
Authentication screen
Riposte now ships with a built-in authentication screen that runs alongside your deployment at /auth/authorize. The product creates short-lived sessions and returns links that you can hand to end users—no external services required. The screen detects the correct provider based on the email address, renders a consent experience, and drives the entire PKCE exchange on the server.
Terminology to avoid confusion:
- Provider callback URI: The OAuth callback URL registered with Google or Microsoft that Riposte listens on to receive the authorization code (e.g.,
http://localhost:8080/providers/gmail/callback). Configure this underriposte.oauth.<provider>.redirectUrior the matching environment variable. - App redirect URL: The user-facing URL in your product where the browser should land after Riposte finishes authentication (e.g.,
https://app.example.com/connect/success). Configure this underriposte.app.redirectUrlor pass it when creating an auth session.
Once the provider completes the handshake, Riposte exchanges the code for tokens, associates the account with the originating session, and redirects the browser to your app redirect URL.
Customize the experience under riposte.authScreen inside riposte/sync.config.json. All of the values live in source control so you can review branding changes just like any other configuration update:
{
"riposte": {
"authScreen": {
"enabled": true,
"publicUrl": "https://mail.example.com",
"brandName": "Acme Support",
"logoText": "AS",
"title": "Connect your email",
"subtitle": "We will securely connect your inbox for {{brand}}.",
"supportEmail": "[email protected]",
"submitButtonLabel": "Continue",
"customCssFile": "./branding/auth-screen.css",
"cors": {
"origin": "*",
},
},
},
}Point customCssFile at a stylesheet to control colors, fonts, or layout. The file is reloaded whenever it changes so you can iterate quickly in development. When enabled is set to false the server returns 503 for new session requests to make it clear that the experience has been turned off.
By default the screen is accessible from any origin. Set riposte.authScreen.cors.origin to a specific origin (or list of origins) if you need to lock it down for production embeds.
🧪 Live provider smoke tests
The repository includes a Vitest suite that exercises real providers end-to-end. Enable it locally by setting RUN_LIVE_PROVIDER_TESTS=true and pointing the harness at one or more seeded accounts via either PROVIDER_TEST_RUNS (JSON array) or the simpler PROVIDER_TEST_SENDER_ID/PROVIDER_TEST_RECIPIENT_IDS flags described in product/src/__tests__/integration/live-provider.integration.test.ts.
When calendar access is provisioned for the connected account the harness now validates basic calendar CRUD in addition to email send/receive. Specify PROVIDER_TEST_CALENDAR_ID to force the test to use a specific calendar; otherwise the first calendar returned by the provider will be used. Temporary events are deleted automatically even if an assertion fails.
🔐 Licensing
Riposte now enforces a license key for deployments that connect more than 100 accounts. Without a valid license the server will refuse new authorizations once 100 accounts are connected. A valid license lifts that limit (or applies a custom max if specified) and is verified with an Ed25519 signature at startup.
Configure license verification
Set the following environment variable (typically inside riposte/.env):
| Variable | Description |
| --------------------- | -------------------------------------------- |
| RIPOSTE_LICENSE_KEY | The signed license string issued by Riposte. |
The verifier ships with a baked-in Ed25519 public key (product/src/common/license-authority.ts). Riposte ignores runtime overrides unless the process runs under NODE_ENV=test so operators cannot self-issue licenses by swapping keys. Unit tests rely on that test-only escape hatch to inject ephemeral key pairs.
Note: If no valid license is configured the server logs a warning at startup and enforces the 100-account cap automatically.
Generate a signing key pair
Use the bundled helper to create an Ed25519 key pair for signing licenses:
npm run license -- keypair --output ./license-keys
# or omit --output to print keys to the console onlyThe command prints:
riposte-license-private-key.pem(PKCS8 private key for signing)riposte-license-public-key.pem(SPKI public key)- Base64url versions of the public key for reference
After generating a new pair, update product/src/common/license-authority.ts and license/canonical-public-key.raw with the printed base64url raw public key. Distribute the corresponding private key securely for license issuance; it should never be committed to git.
Sign a license key
Create licenses with custom expirations and limits using the same helper:
npm run license -- sign \
--expires 2025-12-31 \
--max-accounts 1000 \
--name "Acme Corp" \
--id enterprise-2025 \
--private-key-path ./license-keys/riposte-license-private-key.pemKey details:
--expiresaccepts ISO-8601 dates or relative durations such as180dor2y.--max-accountsis optional; omit it for unlimited accounts on that license.--nameand--idembed metadata that appears in server logs when the license is loaded.- Provide the private key via
--private-key,--private-key-path, or the environment variablesRIPOSTE_LICENSE_PRIVATE_KEY(_PATH).
The command prints the signed license payload and the final RIPOSTE_LICENSE_KEY value to distribute.
🔄 Migration
When you're ready to scale from SQLite to PostgreSQL:
riposte-migrate --to postgresqlThis will:
- Back up your SQLite data
- Update configuration to PostgreSQL
- Restart the service
- Validate the migration
🏗️ Architecture
Core Components
- API Server: Fastify-based REST API (port 8080)
- Admin Portal: Next.js web interface served from its own HTTP listener (auto-selected port, printed on startup)
- Database: SQLite (dev) or PostgreSQL (prod)
- Cache/Queues: In-memory (dev) or Redis (prod)
Supported Providers
- Gmail: Full sync, labels, drafts, send
- Microsoft: Full sync, folders, drafts, send
- IMAP: Mailbox sync and raw message access (bring your own IMAP server)
IMAP provider setup
IMAP support is designed for legacy mailboxes that expose username/password or app-specific password credentials. Because IMAP does not provide OAuth, credentials are stored as provider tokens. To enable IMAP:
Install the optional runtime dependencies in the environment where Riposte runs:
npm install imapflow mailparserCreate an account in the
ProviderAuthTokentable (via the admin UI or directly through the database) using a JSON payload in theaccessTokenfield. Example:{ "host": "imap.example.com", "port": 993, "secure": true, "username": "[email protected]", "password": "app-specific-password", "mailbox": "INBOX" }Additional
tlsoptions (for example{"rejectUnauthorized": false}) can be provided if your server uses a self-signed certificate.Queue a historical sync or use the
/syncendpoints to pull messages. IMAP support currently focuses on read-only synchronization and raw message retrieval. Sending, label management, and calendar/contacts are not yet implemented for IMAP accounts.
Key Features
- Real-time sync: Push notifications + polling
- Rate limiting: Global queue with provider limits
- Horizontal scaling: Redis-based distributed system
- Data sovereignty: Everything stays on-premise
- Cost savings: 90% cheaper than Cloud-APIs
📊 Business Value
Cost Comparison
- Cloud-APIs: $1.50 per connected account
- Riposte: $99/month unlimited accounts
- Savings: 90% cost reduction
Target Market
- SMBs: Priced out of Cloud-APIs
- Enterprises: Want cost savings + data control
- Agencies: Managing multiple clients
- Developers: Building CRMs, ATSs, etc.
🔧 Development
Prerequisites
- Node.js 18+
- npm or yarn
- PostgreSQL (for production)
- Redis (for production)
- [Optional]
better-sqlite3when running against SQLite (install withnpm install better-sqlite3)
Setup
# Clone the repository
git clone <private-repo>
cd riposte
# Install dependencies
npm install
# Set up development environment
npm run setup
# Start development server
npm run devNotifications
Each provider can use either webhook deliveries or polling to detect mailbox changes. Configure the preference in the notifications section of your sync.config.json (or via the corresponding environment variables). Gmail defaults to webhooks, while Microsoft starts in polling mode so the integration works without hosting a public webhook endpoint. Switch Microsoft to webhooks by setting notifications.providers.microsoft.mode to "webhook" once you have a reachable HTTPS callback.
The built-in polling engine runs automatically for every provider configured with mode: "polling". Tune its behaviour under notifications.polling:
{
"notifications": {
"polling": {
"intervalMs": 60000, // How often Riposte polls each account
"batchSize": 25, // Messages fetched per account during a cycle
"initialLookbackMs": 900000, // Look back window for the very first poll (in ms)
"maxAccountsPerCycle": 10, // Accounts processed per polling loop
},
},
}Override the same values via environment variables:
NOTIFICATIONS_POLLING_INTERVAL_MS=60000
NOTIFICATIONS_POLLING_BATCH_SIZE=25
NOTIFICATIONS_POLLING_INITIAL_LOOKBACK_MS=900000
NOTIFICATIONS_POLLING_MAX_ACCOUNTS=10Whenever a new account connects we enqueue an immediate polling run so you receive message.received events without waiting for the next interval.
Building for Production
# Build everything
npm run build
# Start production server
npm start📝 API Documentation
Once running, visit:
- Swagger UI: http://localhost:8080/docs
- API Reference: http://localhost:8080/documentation/json
🛡️ Security
- API Key Authentication: All endpoints require API key
- Data Encryption: Sensitive data encrypted at rest
- On-premise Only: No external data sharing
- Token Management: Secure OAuth token storage
📈 Monitoring
- Health Check:
GET /healthz - Metrics: Built-in performance monitoring
- Logs: Structured logging with Pino
- Alerts: Configurable error notifications
🤝 Support
For support and questions:
- Documentation: See
/docsendpoint - Issues: Contact support team
- Updates:
npm update riposte
📄 License
UNLICENSED - Proprietary software
Riposte - The Cloud-API alternative that saves you 90% on email integration costs.
