Self-Hosted Deployment Guide
Deploy the full Wraps control plane to your own AWS account. API, dashboard, database, and email infrastructure — everything in your infrastructure.
Deploy the full Wraps control plane to your own AWS account. Your API, your dashboard, your database — everything in your infrastructure with no Wraps servers in the critical path.
Self-hosting deploys the full Wraps control plane — API Lambda, dashboard, SQS workers, DynamoDB, and EventBridge Scheduler — into your AWS account using SST. You fork the Wraps repository, configure a handful of secrets, and run a single command. Everything runs in your account; no Wraps SaaS servers are in the critical path.
Fork the Wraps repo, run pnpm selfhost:deploy, and receive upstream updates on your schedule via a weekly sync PR
Deployed into your AWS account via SST — you own the function, logs, and billing
Bring any Postgres-compatible DB (Neon, Supabase, Railway, self-hosted)
The apps/web dashboard is deployed automatically via SST into your AWS account — no separate hosting needed
Automated upgrades use GitHub Actions OIDC to assume a short-lived AWS role — no stored keys required
The self-hosted deployment scripts live in the Wraps monorepo. Fork it so you can receive upstream updates on your own schedule.
github.com/wraps-team/wraps and click Fork. Keep the default branch name main.git clone https://github.com/YOUR_ORG/wraps.gitcd wrapspnpm installRun the deploy script from the root of your fork. It provisions all AWS infrastructure via SST, runs database migrations, and writes an .env.selfhost file with the outputs.
# Pass all required values as flags (non-interactive):pnpm selfhost:deploy \ --database-url "postgres://user:pass@your-db.neon.tech/wraps" \ --license-key "wraps_lic_..." \ --yes# Or run without flags — the script will prompt you:pnpm selfhost:deployThe script will:
.env.selfhost with auto-generated secretssst install then deploy the Lambda, dashboard, and supporting AWS resources via SST.env.selfhostRun migrations after initial deploy
selfhost:deploy provisions infrastructure but does not run database migrations. After your first deploy, run pnpm selfhost:upgrade to apply all pending Drizzle migrations against your database. Upgrades always run migrations automatically.
After the deploy completes, your .env.selfhost will look like this:
# Created automatically by pnpm selfhost:deployDATABASE_URL=postgres://user:pass@your-db.neon.tech/wrapsBETTER_AUTH_SECRET=<auto-generated>UNSUBSCRIBE_SECRET=<auto-generated>LICENSE_KEY=wraps_lic_...# Set when --web-domain flag is provided:SELFHOST_WEB_DOMAIN=dashboard.yourdomain.com# Populated after SST deploy completes:# Without --web-domain, NEXT_PUBLIC_APP_URL is a *.cloudfront.net URL:NEXT_PUBLIC_APP_URL=https://d1abc123xyz.cloudfront.netWRAPS_API_URL=https://abc123.lambda-url.us-east-1.on.awsBETTER_AUTH_URL=https://d1abc123xyz.cloudfront.net# Populated if email infrastructure was found:WRAPS_EMAIL_ROLE_ARN=arn:aws:iam::123456789012:role/wraps-email-roleAUTH_EMAIL_CONFIGURATION_SET=wraps-email-eventsAUTH_EMAIL_FROM=noreply@yourdomain.comOptional flags
Pass --web-domain to set a custom domain for the dashboard (e.g. dashboard.yourdomain.com) and --ai-gateway-api-key to enable AI email generation features. Both can be added later by re-running pnpm selfhost:upgrade. Without --web-domain, NEXT_PUBLIC_APP_URL will be a *.cloudfront.net URL — a custom domain can be configured any time by re-running with --web-domain.
The SST stack always deploys to us-east-1. To target a different region, edit the region field in infra/selfhost.sst.config.ts before deploying.
Initialise email sending with AWS SES. This creates IAM roles, configures SES, and sets up event processing for open and click tracking.
wraps email initIf you only need SMS, skip this step and run wraps sms init instead.
With the dashboard running, create your account, then authenticate the CLI against your instance and register the AWS connection.
wraps selfhost loginWhy not wraps auth login?
The standard login command authenticates against the Wraps SaaS platform. wraps selfhost login reads your deployment metadata and authenticates against your own dashboard URL instead.
Once signed in, register this AWS account with your self-hosted control plane. This creates the wraps-console-access-role IAM role and enables event streaming from your email infrastructure to the dashboard:
wraps selfhost connectSingle-account deployments
The CLI automatically configures the trust policy to trust your own AWS account — not the Wraps SaaS platform account.
Two additional commands are available once you are signed in:
wraps selfhost env — prints the environment variables needed to deploy apps/web against your self-hosted control plane (useful for Vercel or any external hosting).wraps selfhost logout — removes the local session token for your self-hosted instance.Check the health of your self-hosted control plane from the CLI:
wraps selfhost statusA healthy deployment reports the API URL, app URL, AWS region, deployment timestamp, and license key prefix. Open your dashboard URL and confirm your connected AWS account and any email or SMS infrastructure are visible.
You are fully self-hosted
All API calls from the dashboard go directly to your Lambda. No Wraps SaaS servers are involved at runtime. Your license is verified offline — no phone-home required.
New Wraps releases include Lambda updates, database migrations, and dashboard improvements. Choose the upgrade path that fits your workflow.
Pull upstream changes into your fork, then run the upgrade script. It re-deploys the SST stack and runs any pending database migrations automatically.
# Manual upgrade from your fork:pnpm selfhost:upgrade# Runs SST deploy + database migrations automaticallyThe upgrade script redeploys both the Lambda and the dashboard in a single run — no separate steps required.
Your fork includes two pre-built workflows that handle the full update loop hands-free:
selfhost-sync.yml
Runs every Monday at 09:00 UTC (also manually triggerable). Fetches upstream main, opens a PR with up to 20 included commits. Any existing sync PR is automatically closed and superseded. You review and merge when ready.
selfhost-deploy.yml
Manual trigger only. Runs pnpm selfhost:upgrade (or deploy for first-time) with OIDC credentials — no stored AWS keys.
Follow these steps to enable them in your fork.
GitHub disables Actions on new forks by default. Go to your fork's Settings → Actions → General and set "Allow all actions and reusable workflows", then save.
See the GitHub OIDC with AWS guide and the AWS IAM OIDC provider docs for full context. In the AWS Console, go to IAM → Identity providers → Add provider:
https://token.actions.githubusercontent.comsts.amazonaws.comThen create an IAM role named wraps-github-deploy-role (or similar) with the following trust policy. Replace YOUR_ACCOUNT_ID and YOUR_GITHUB_ORG with your AWS account ID and the GitHub username or org that owns your fork:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::YOUR_ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" }, "StringLike": { "token.actions.githubusercontent.com:sub": "repo:YOUR_GITHUB_ORG/wraps:*" } } } ]}Attach an IAM permissions policy to this role with the permissions SST needs: Lambda, S3, CloudFormation, IAM, SSM Parameter Store, CloudWatch Logs, STS, CloudFront, SQS, DynamoDB, and EventBridge Scheduler. Copy the role ARN — you will need it in the next step.
The deploy workflow already sets permissions: id-token: write at the workflow level, which is required for OIDC token exchange. You do not need to add this manually.
In your fork, go to Settings → Secrets and variables → Actions and add the following:
After your first deploy
Open .env.selfhost and copy BETTER_AUTH_SECRET and UNSUBSCRIBE_SECRET into repository secrets. The upgrade workflow reconstructs .env.selfhost from secrets on every run — without these, SST will overwrite your Lambda with empty env vars.
Repository secrets
| Secret | Required | Description |
|---|---|---|
AWS_DEPLOY_ROLE_ARN | Always | ARN of the GitHub deploy role from step 2 |
DATABASE_URL | Always | Postgres connection string — needed by SST on every deploy to set the Lambda env var |
WRAPS_LICENSE_KEY | Always | Your Wraps license key — needed by SST on every deploy |
BETTER_AUTH_SECRET | Always | Session signing secret — auto-generated on first deploy. Copy from .env.selfhost after initial deploy. Rotating this invalidates all active sessions |
UNSUBSCRIBE_SECRET | Always | HMAC secret for unsubscribe links — auto-generated on first deploy. Copy from .env.selfhost after initial deploy |
WRAPS_EMAIL_ROLE_ARN | Optional | IAM role ARN for SES sending. Copy from .env.selfhost after running wraps email init |
AUTH_EMAIL_FROM | Optional | From address for transactional auth emails (password reset, magic links) |
AUTH_EMAIL_CONFIGURATION_SET | Optional | SES configuration set for auth emails. Copy from .env.selfhost after running wraps email init |
AI_GATEWAY_API_KEY | Optional | API key for the AI gateway — enables AI email template generation |
Repository variables (not secrets)
| Variable | Default | Description |
|---|---|---|
AWS_REGION | us-east-1 | AWS region for deployments (note: must also match infra/selfhost.sst.config.ts) |
SELFHOST_WEB_DOMAIN | — | Custom domain for the dashboard (e.g. dashboard.yourdomain.com). When set, SST configures CloudFront with this domain |
NEXT_PUBLIC_APP_URL | — | Full URL of the dashboard. Set this to your CloudFront URL (or custom domain) after first deploy — required for correct auth redirects on upgrades if not using SELFHOST_WEB_DOMAIN |
Go to Settings → Environments → New environment and create one named production. Enable Required reviewers and add yourself — this adds a manual approval step before any deploy job runs, giving you a second confirmation gate even after you click "Run workflow".
wraps-team/wraps. Review the changes and merge when you are ready.upgrade (or deploy for a first-time setup) and click Run.production environment with required reviewers, approve the pending deployment. The job then assumes the OIDC role, runs SST deploy, and applies any pending migrations.The deploy script creates and maintains .env.selfhost in your repo root. These values are read by SST at deploy time and injected as Lambda environment variables.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | Yes | Postgres connection string for the control plane database |
LICENSE_KEY | Yes | Your Wraps enterprise license key (wraps_lic_...) |
BETTER_AUTH_SECRET | Yes | Secret for session signing — auto-generated on first deploy, do not rotate without invalidating all active sessions |
UNSUBSCRIBE_SECRET | Yes | HMAC secret used to sign unsubscribe links in outbound emails — auto-generated on first deploy |
NEXT_PUBLIC_APP_URL | Yes | Full URL of your dashboard (e.g. https://dashboard.yourdomain.com) — used for CORS and auth redirects. Set automatically after deploy |
WRAPS_API_URL | Yes | Lambda function URL — set automatically after first deploy |
BETTER_AUTH_URL | Yes | Same as NEXT_PUBLIC_APP_URL — set automatically after first deploy |
SELFHOST_WEB_DOMAIN | Optional | Custom domain for the dashboard — sets AUTH_EMAIL_FROM to noreply@yourdomain.com automatically. Pass via --web-domain flag |
WRAPS_EMAIL_ROLE_ARN | Optional | IAM role ARN for SES sending — populated automatically when email infrastructure is present |
AUTH_EMAIL_FROM | Optional | From address for transactional auth emails (password reset, magic links). Must be a verified SES sender |
AUTH_EMAIL_CONFIGURATION_SET | Optional | SES configuration set name for auth emails — populated automatically when email infrastructure is present |
AI_GATEWAY_API_KEY | Optional | API key for the AI gateway — enables the AI email template generation feature. Pass via --ai-gateway-api-key flag |
ANTHROPIC_API_KEY | Optional | Anthropic API key — alternative to AI_GATEWAY_API_KEY for direct Anthropic access without a gateway |
AI_MODEL | Optional | Override the default Claude model used for AI features (e.g. claude-sonnet-4-6) |
Add DKIM, SPF, and DMARC records to start sending from your own domain.
Domain verification →SES starts in sandbox mode. Request production access to send to any recipient.
Production access →Add AI_GATEWAY_API_KEY to your .env.selfhost and run pnpm selfhost:upgrade to enable AI-powered email template generation.
Remove the SST-managed control plane (Lambda, CloudFront, S3, SQS, DynamoDB, EventBridge Scheduler) and clears local metadata. Email infrastructure created by wraps email init (SES config sets, IAM roles, EventBridge rules) is managed separately and must be removed via the CLI or AWS Console.
pnpm selfhost:destroy