diff --git a/components/dashboard/application/advanced/traefik/show-traefik-config.tsx b/components/dashboard/application/advanced/traefik/show-traefik-config.tsx
index 6b5415044..a3fb4f302 100644
--- a/components/dashboard/application/advanced/traefik/show-traefik-config.tsx
+++ b/components/dashboard/application/advanced/traefik/show-traefik-config.tsx
@@ -29,7 +29,7 @@ export const ShowTraefikConfig = ({ applicationId }: Props) => {
Traefik
Modify the traefik config, in rare cases you may need to add
- specific config, becarefull because modifying incorrectly can break
+ specific config, be careful because modifying incorrectly can break
traefik and your application
diff --git a/package.json b/package.json
index 5883df66e..502ff347c 100644
--- a/package.json
+++ b/package.json
@@ -1,159 +1,159 @@
{
- "name": "dokploy",
- "version": "v0.3.1",
- "private": true,
- "license": "Apache-2.0",
- "type": "module",
- "scripts": {
- "build": "npm run build-server && npm run build-next",
- "start": "node dist/server.mjs",
- "build-server": "tsx esbuild.config.ts",
- "build-next": "next build",
- "setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
- "reset-password": "node dist/reset-password.mjs",
- "dev": "tsx watch -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
- "studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
- "migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
- "migration:run": "tsx -r dotenv/config migration.ts",
- "migration:up": "drizzle-kit up --config ./server/db/drizzle.config.ts",
- "migration:drop": "drizzle-kit drop --config ./server/db/drizzle.config.ts",
- "db:push": "drizzle-kit --config ./server/db/drizzle.config.ts",
- "db:truncate": "tsx -r dotenv/config ./server/db/reset.ts",
- "db:studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
- "lint": "biome lint",
- "db:seed": "tsx -r dotenv/config ./server/db/seed.ts",
- "db:clean": "tsx -r dotenv/config ./server/db/reset.ts",
- "docker:build": "./docker/build.sh",
- "docker:push": "./docker/push.sh",
- "docker:build:canary": "./docker/build.sh canary",
- "docker:push:canary": "./docker/push.sh canary",
- "version": "echo $(node -p \"require('./package.json').version\")",
- "test": "vitest --config __test__/vitest.config.ts"
- },
- "dependencies": {
- "@aws-sdk/client-s3": "3.515.0",
- "@codemirror/lang-json": "^6.0.1",
- "@codemirror/lang-yaml": "^6.1.1",
- "@codemirror/language": "^6.10.1",
- "@codemirror/legacy-modes": "6.4.0",
- "@dokploy/trpc-openapi": "0.0.4",
- "@faker-js/faker": "^8.4.1",
- "@hookform/resolvers": "^3.3.4",
- "@lucia-auth/adapter-drizzle": "1.0.7",
- "@octokit/auth-app": "^6.0.4",
- "@octokit/webhooks": "^13.2.7",
- "@radix-ui/react-accordion": "1.1.2",
- "@radix-ui/react-alert-dialog": "^1.0.5",
- "@radix-ui/react-avatar": "^1.0.4",
- "@radix-ui/react-checkbox": "^1.0.4",
- "@radix-ui/react-dialog": "^1.0.5",
- "@radix-ui/react-dropdown-menu": "^2.0.6",
- "@radix-ui/react-label": "^2.0.2",
- "@radix-ui/react-popover": "^1.0.7",
- "@radix-ui/react-progress": "^1.0.3",
- "@radix-ui/react-radio-group": "^1.1.3",
- "@radix-ui/react-scroll-area": "^1.0.5",
- "@radix-ui/react-select": "^2.0.0",
- "@radix-ui/react-separator": "^1.0.3",
- "@radix-ui/react-slot": "^1.0.2",
- "@radix-ui/react-switch": "^1.0.3",
- "@radix-ui/react-tabs": "^1.0.4",
- "@radix-ui/react-toggle": "^1.0.3",
- "@radix-ui/react-tooltip": "^1.0.7",
- "@tanstack/react-query": "^4.36.1",
- "@tanstack/react-table": "^8.16.0",
- "@trpc/client": "^10.43.6",
- "@trpc/next": "^10.43.6",
- "@trpc/react-query": "^10.43.6",
- "@trpc/server": "^10.43.6",
- "@uiw/codemirror-theme-github": "^4.22.1",
- "@uiw/react-codemirror": "^4.22.1",
- "@xterm/addon-attach": "0.10.0",
- "@xterm/xterm": "^5.4.0",
- "bcrypt": "5.1.1",
- "bl": "6.0.11",
- "boxen": "^7.1.1",
- "bullmq": "5.4.2",
- "class-variance-authority": "^0.7.0",
- "clsx": "^2.1.0",
- "cmdk": "^0.2.0",
- "copy-to-clipboard": "^3.3.3",
- "copy-webpack-plugin": "^12.0.2",
- "date-fns": "3.6.0",
- "dockerode": "4.0.2",
- "dockerode-compose": "^1.4.0",
- "dockerstats": "2.4.2",
- "dotenv": "16.4.5",
- "drizzle-orm": "^0.30.8",
- "drizzle-zod": "0.5.1",
- "hi-base32": "^0.5.1",
- "input-otp": "^1.2.4",
- "js-yaml": "4.1.0",
- "k6": "^0.0.0",
- "lodash": "4.17.21",
- "lucia": "^3.0.1",
- "lucide-react": "^0.312.0",
- "nanoid": "3",
- "next": "^14.1.3",
- "next-themes": "^0.2.1",
- "node-os-utils": "1.3.7",
- "node-pty": "1.0.0",
- "node-schedule": "2.1.1",
- "octokit": "3.1.2",
- "otpauth": "^9.2.3",
- "postgres": "3.4.4",
- "public-ip": "6.0.2",
- "qrcode": "^1.5.3",
- "react": "18.2.0",
- "react-dom": "18.2.0",
- "react-hook-form": "^7.49.3",
- "recharts": "^2.12.3",
- "slugify": "^1.6.6",
- "sonner": "^1.4.0",
- "superjson": "^2.2.1",
- "swagger-ui-react": "^5.17.14",
- "tailwind-merge": "^2.2.0",
- "tailwindcss-animate": "^1.0.7",
- "tar-fs": "3.0.5",
- "use-resize-observer": "9.1.0",
- "ws": "8.16.0",
- "xterm-addon-fit": "^0.8.0",
- "zod": "^3.23.4"
- },
- "devDependencies": {
- "@biomejs/biome": "1.7.1",
- "@types/bcrypt": "5.0.2",
- "@types/dockerode": "3.3.23",
- "@types/js-yaml": "4.0.9",
- "@types/lodash": "4.17.4",
- "@types/node": "^18.17.0",
- "@types/node-os-utils": "1.3.4",
- "@types/node-schedule": "2.1.6",
- "@types/qrcode": "^1.5.5",
- "@types/react": "^18.2.37",
- "@types/react-dom": "^18.2.15",
- "@types/swagger-ui-react": "^4.18.3",
- "@types/tar-fs": "2.0.4",
- "@types/ws": "8.5.10",
- "autoprefixer": "^10.4.14",
- "drizzle-kit": "^0.21.1",
- "esbuild": "0.20.2",
- "localtunnel": "2.0.2",
- "postcss": "^8.4.31",
- "prettier": "^3.2.4",
- "prettier-plugin-tailwindcss": "^0.5.11",
- "tailwindcss": "^3.4.1",
- "tsx": "^4.7.0",
- "typescript": "^5.4.2",
- "vite-tsconfig-paths": "4.3.2",
- "vitest": "^1.6.0",
- "xterm-readline": "1.1.1"
- },
- "ct3aMetadata": {
- "initVersion": "7.25.2"
- },
- "engines": {
- "node": "^18.18.0",
- "pnpm": ">=8.15.4"
- }
+ "name": "dokploy",
+ "version": "v0.3.2",
+ "private": true,
+ "license": "Apache-2.0",
+ "type": "module",
+ "scripts": {
+ "build": "npm run build-server && npm run build-next",
+ "start": "node dist/server.mjs",
+ "build-server": "tsx esbuild.config.ts",
+ "build-next": "next build",
+ "setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
+ "reset-password": "node dist/reset-password.mjs",
+ "dev": "tsx watch -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
+ "studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
+ "migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
+ "migration:run": "tsx -r dotenv/config migration.ts",
+ "migration:up": "drizzle-kit up --config ./server/db/drizzle.config.ts",
+ "migration:drop": "drizzle-kit drop --config ./server/db/drizzle.config.ts",
+ "db:push": "drizzle-kit --config ./server/db/drizzle.config.ts",
+ "db:truncate": "tsx -r dotenv/config ./server/db/reset.ts",
+ "db:studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
+ "lint": "biome lint",
+ "db:seed": "tsx -r dotenv/config ./server/db/seed.ts",
+ "db:clean": "tsx -r dotenv/config ./server/db/reset.ts",
+ "docker:build": "./docker/build.sh",
+ "docker:push": "./docker/push.sh",
+ "docker:build:canary": "./docker/build.sh canary",
+ "docker:push:canary": "./docker/push.sh canary",
+ "version": "echo $(node -p \"require('./package.json').version\")",
+ "test": "vitest --config __test__/vitest.config.ts"
+ },
+ "dependencies": {
+ "@aws-sdk/client-s3": "3.515.0",
+ "@codemirror/lang-json": "^6.0.1",
+ "@codemirror/lang-yaml": "^6.1.1",
+ "@codemirror/language": "^6.10.1",
+ "@codemirror/legacy-modes": "6.4.0",
+ "@dokploy/trpc-openapi": "0.0.4",
+ "@faker-js/faker": "^8.4.1",
+ "@hookform/resolvers": "^3.3.4",
+ "@lucia-auth/adapter-drizzle": "1.0.7",
+ "@octokit/auth-app": "^6.0.4",
+ "@octokit/webhooks": "^13.2.7",
+ "@radix-ui/react-accordion": "1.1.2",
+ "@radix-ui/react-alert-dialog": "^1.0.5",
+ "@radix-ui/react-avatar": "^1.0.4",
+ "@radix-ui/react-checkbox": "^1.0.4",
+ "@radix-ui/react-dialog": "^1.0.5",
+ "@radix-ui/react-dropdown-menu": "^2.0.6",
+ "@radix-ui/react-label": "^2.0.2",
+ "@radix-ui/react-popover": "^1.0.7",
+ "@radix-ui/react-progress": "^1.0.3",
+ "@radix-ui/react-radio-group": "^1.1.3",
+ "@radix-ui/react-scroll-area": "^1.0.5",
+ "@radix-ui/react-select": "^2.0.0",
+ "@radix-ui/react-separator": "^1.0.3",
+ "@radix-ui/react-slot": "^1.0.2",
+ "@radix-ui/react-switch": "^1.0.3",
+ "@radix-ui/react-tabs": "^1.0.4",
+ "@radix-ui/react-toggle": "^1.0.3",
+ "@radix-ui/react-tooltip": "^1.0.7",
+ "@tanstack/react-query": "^4.36.1",
+ "@tanstack/react-table": "^8.16.0",
+ "@trpc/client": "^10.43.6",
+ "@trpc/next": "^10.43.6",
+ "@trpc/react-query": "^10.43.6",
+ "@trpc/server": "^10.43.6",
+ "@uiw/codemirror-theme-github": "^4.22.1",
+ "@uiw/react-codemirror": "^4.22.1",
+ "@xterm/addon-attach": "0.10.0",
+ "@xterm/xterm": "^5.4.0",
+ "bcrypt": "5.1.1",
+ "bl": "6.0.11",
+ "boxen": "^7.1.1",
+ "bullmq": "5.4.2",
+ "class-variance-authority": "^0.7.0",
+ "clsx": "^2.1.0",
+ "cmdk": "^0.2.0",
+ "copy-to-clipboard": "^3.3.3",
+ "copy-webpack-plugin": "^12.0.2",
+ "date-fns": "3.6.0",
+ "dockerode": "4.0.2",
+ "dockerode-compose": "^1.4.0",
+ "dockerstats": "2.4.2",
+ "dotenv": "16.4.5",
+ "drizzle-orm": "^0.30.8",
+ "drizzle-zod": "0.5.1",
+ "hi-base32": "^0.5.1",
+ "input-otp": "^1.2.4",
+ "js-yaml": "4.1.0",
+ "k6": "^0.0.0",
+ "lodash": "4.17.21",
+ "lucia": "^3.0.1",
+ "lucide-react": "^0.312.0",
+ "nanoid": "3",
+ "next": "^14.1.3",
+ "next-themes": "^0.2.1",
+ "node-os-utils": "1.3.7",
+ "node-pty": "1.0.0",
+ "node-schedule": "2.1.1",
+ "octokit": "3.1.2",
+ "otpauth": "^9.2.3",
+ "postgres": "3.4.4",
+ "public-ip": "6.0.2",
+ "qrcode": "^1.5.3",
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
+ "react-hook-form": "^7.49.3",
+ "recharts": "^2.12.3",
+ "slugify": "^1.6.6",
+ "sonner": "^1.4.0",
+ "superjson": "^2.2.1",
+ "swagger-ui-react": "^5.17.14",
+ "tailwind-merge": "^2.2.0",
+ "tailwindcss-animate": "^1.0.7",
+ "tar-fs": "3.0.5",
+ "use-resize-observer": "9.1.0",
+ "ws": "8.16.0",
+ "xterm-addon-fit": "^0.8.0",
+ "zod": "^3.23.4"
+ },
+ "devDependencies": {
+ "@biomejs/biome": "1.7.1",
+ "@types/bcrypt": "5.0.2",
+ "@types/dockerode": "3.3.23",
+ "@types/js-yaml": "4.0.9",
+ "@types/lodash": "4.17.4",
+ "@types/node": "^18.17.0",
+ "@types/node-os-utils": "1.3.4",
+ "@types/node-schedule": "2.1.6",
+ "@types/qrcode": "^1.5.5",
+ "@types/react": "^18.2.37",
+ "@types/react-dom": "^18.2.15",
+ "@types/swagger-ui-react": "^4.18.3",
+ "@types/tar-fs": "2.0.4",
+ "@types/ws": "8.5.10",
+ "autoprefixer": "^10.4.14",
+ "drizzle-kit": "^0.21.1",
+ "esbuild": "0.20.2",
+ "localtunnel": "2.0.2",
+ "postcss": "^8.4.31",
+ "prettier": "^3.2.4",
+ "prettier-plugin-tailwindcss": "^0.5.11",
+ "tailwindcss": "^3.4.1",
+ "tsx": "^4.7.0",
+ "typescript": "^5.4.2",
+ "vite-tsconfig-paths": "4.3.2",
+ "vitest": "^1.6.0",
+ "xterm-readline": "1.1.1"
+ },
+ "ct3aMetadata": {
+ "initVersion": "7.25.2"
+ },
+ "engines": {
+ "node": "^18.18.0",
+ "pnpm": ">=8.15.4"
+ }
}
diff --git a/public/templates/doublezero.svg b/public/templates/doublezero.svg
new file mode 100644
index 000000000..e28cbeb09
--- /dev/null
+++ b/public/templates/doublezero.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/templates/listmonk.png b/public/templates/listmonk.png
new file mode 100644
index 000000000..cd6f5618e
Binary files /dev/null and b/public/templates/listmonk.png differ
diff --git a/server/setup/postgres-setup.ts b/server/setup/postgres-setup.ts
index ce5bfccc2..d292f877e 100644
--- a/server/setup/postgres-setup.ts
+++ b/server/setup/postgres-setup.ts
@@ -26,6 +26,9 @@ export const initializePostgres = async () => {
RestartPolicy: {
Condition: "on-failure",
},
+ Placement: {
+ Constraints: ["node.role==manager"],
+ },
},
Mode: {
Replicated: {
diff --git a/server/setup/redis-setup.ts b/server/setup/redis-setup.ts
index 40886bd09..82f568e46 100644
--- a/server/setup/redis-setup.ts
+++ b/server/setup/redis-setup.ts
@@ -23,6 +23,9 @@ export const initializeRedis = async () => {
RestartPolicy: {
Condition: "on-failure",
},
+ Placement: {
+ Constraints: ["node.role==manager"],
+ },
},
Mode: {
Replicated: {
diff --git a/server/setup/registry-setup.ts b/server/setup/registry-setup.ts
index 94d592f7f..085ed365d 100644
--- a/server/setup/registry-setup.ts
+++ b/server/setup/registry-setup.ts
@@ -43,6 +43,9 @@ export const initializeRegistry = async (
RestartPolicy: {
Condition: "on-failure",
},
+ Placement: {
+ Constraints: ["node.role==manager"],
+ },
},
Mode: {
Replicated: {
diff --git a/server/setup/traefik-setup.ts b/server/setup/traefik-setup.ts
index 889988d66..4cd011cd4 100644
--- a/server/setup/traefik-setup.ts
+++ b/server/setup/traefik-setup.ts
@@ -41,6 +41,9 @@ export const initializeTraefik = async () => {
RestartPolicy: {
Condition: "on-failure",
},
+ Placement: {
+ Constraints: ["node.role==manager"],
+ },
},
Mode: {
Replicated: {
diff --git a/templates/doublezero/docker-compose.yml b/templates/doublezero/docker-compose.yml
new file mode 100644
index 000000000..bb7b1d2b3
--- /dev/null
+++ b/templates/doublezero/docker-compose.yml
@@ -0,0 +1,31 @@
+services:
+ doublezero:
+ restart: always
+ image: liltechnomancer/double-zero:0.2.1
+ ports:
+ - ${DOUBLEZERO_PORT}
+ networks:
+ - dokploy-network
+ volumes:
+ - db-data:/var/lib/doublezero/data
+ environment:
+ AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
+ AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
+ AWS_REGION: ${AWS_REGION}
+ SQS_URL: ${SQS_URL}
+ SYSTEM_EMAIL: ${SYSTEM_EMAIL}
+ SECRET_KEY_BASE: ${SECRET_KEY_BASE}
+ PHX_HOST: ${DOUBLEZERO_HOST}
+ DATABASE_PATH: ./00.db
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.${HASH}.rule=Host(`${DOUBLEZERO_HOST}`)"
+ - "traefik.http.services.${HASH}.loadbalancer.server.port=${DOUBLEZERO_PORT}"
+
+volumes:
+ db-data:
+ driver: local
+
+networks:
+ dokploy-network:
+ external: true
diff --git a/templates/doublezero/index.ts b/templates/doublezero/index.ts
new file mode 100644
index 000000000..72304728f
--- /dev/null
+++ b/templates/doublezero/index.ts
@@ -0,0 +1,29 @@
+import {
+ generateHash,
+ generateRandomDomain,
+ type Template,
+ type Schema,
+ generateBase64,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainServiceHash = generateHash(schema.projectName);
+ const randomDomain = generateRandomDomain(schema);
+ const secretKeyBase = generateBase64(64);
+
+ const envs = [
+ `DOUBLEZERO_HOST=${randomDomain}`,
+ "DOUBLEZERO_PORT=4000",
+ `HASH=${mainServiceHash}`,
+ `SECRET_KEY_BASE=${secretKeyBase}`,
+ "AWS_ACCESS_KEY_ID=your-aws-access-key",
+ "AWS_SECRET_ACCESS_KEY=your-aws-secret-key",
+ "AWS_REGION=your-aws-region",
+ "SQS_URL=your-aws-sqs-url",
+ "SYSTEM_EMAIL=",
+ ];
+
+ return {
+ envs,
+ };
+}
diff --git a/templates/listmonk/docker-compose.yml b/templates/listmonk/docker-compose.yml
new file mode 100644
index 000000000..e17b76577
--- /dev/null
+++ b/templates/listmonk/docker-compose.yml
@@ -0,0 +1,56 @@
+services:
+ db:
+ image: postgres:13
+ ports:
+ - 5432
+ networks:
+ - dokploy-network
+ environment:
+ - POSTGRES_PASSWORD=listmonk
+ - POSTGRES_USER=listmonk
+ - POSTGRES_DB=listmonk
+ restart: unless-stopped
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U listmonk"]
+ interval: 10s
+ timeout: 5s
+ retries: 6
+ volumes:
+ - listmonk-data:/var/lib/postgresql/data
+
+ setup:
+ image: listmonk/listmonk:v3.0.0
+ networks:
+ - dokploy-network
+ volumes:
+ - ./config.toml:/listmonk/config.toml
+ depends_on:
+ - db
+ command: [sh, -c, "sleep 3 && ./listmonk --install --idempotent --yes --config config.toml"]
+
+ app:
+ restart: unless-stopped
+ image: listmonk/listmonk:v3.0.0
+ ports:
+ - "${LISTMONK_PORT}"
+ networks:
+ - dokploy-network
+ environment:
+ - TZ=Etc/UTC
+ depends_on:
+ - db
+ - setup
+ volumes:
+ - ./config.toml:/listmonk/config.toml
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.${HASH}.rule=Host(`${LISTMONK_HOST}`)"
+ - "traefik.http.services.${HASH}.loadbalancer.server.port=${LISTMONK_PORT}"
+
+volumes:
+ listmonk-data:
+ driver: local
+
+networks:
+ dokploy-network:
+ external: true
diff --git a/templates/listmonk/index.ts b/templates/listmonk/index.ts
new file mode 100644
index 000000000..e9df62223
--- /dev/null
+++ b/templates/listmonk/index.ts
@@ -0,0 +1,52 @@
+import {
+ generateHash,
+ generateRandomDomain,
+ type Template,
+ type Schema,
+ generatePassword,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainServiceHash = generateHash(schema.projectName);
+ const randomDomain = generateRandomDomain(schema);
+ const adminPassword = generatePassword(32);
+
+ const envs = [
+ `LISTMONK_HOST=${randomDomain}`,
+ "LISTMONK_PORT=9000",
+ `HASH=${mainServiceHash}`,
+ `# login with admin:${adminPassword}`,
+ "# check config.toml in Advanced / Volumes for more options",
+ ];
+
+ const mounts: Template["mounts"] = [
+ {
+ mountPath: "./config.toml",
+ content: `[app]
+address = "0.0.0.0:9000"
+
+admin_username = "admin"
+admin_password = "${adminPassword}"
+
+[db]
+host = "db"
+port = 5432
+user = "listmonk"
+password = "listmonk"
+database = "listmonk"
+
+ssl_mode = "disable"
+max_open = 25
+max_idle = 25
+max_lifetime = "300s"
+
+params = ""
+`,
+ },
+ ];
+
+ return {
+ envs,
+ mounts,
+ };
+}
diff --git a/templates/templates.ts b/templates/templates.ts
index 49bd596c4..b41a2028b 100644
--- a/templates/templates.ts
+++ b/templates/templates.ts
@@ -331,5 +331,33 @@ export const templates: TemplateData[] = [
},
tags: ['chat'],
load: () => import('./open-webui/index').then((m) => m.generate),
+ },
+ {
+ id: 'listmonk',
+ name: 'Listmonk',
+ version: 'v3.0.0',
+ description: 'High performance, self-hosted, newsletter and mailing list manager with a modern dashboard.',
+ logo: 'listmonk.png',
+ links: {
+ github: 'https://github.com/knadh/listmonk',
+ website: 'https://listmonk.app/',
+ docs: 'https://listmonk.app/docs/',
+ },
+ tags: ['email', 'newsletter', 'mailing-list'],
+ load: () => import('./listmonk/index').then((m) => m.generate),
+ },
+ {
+ id: 'doublezero',
+ name: 'Double Zero',
+ version: 'v0.2.1',
+ description: '00 is a self hostable SES dashboard for sending and monitoring emails with AWS',
+ logo: 'doublezero.svg',
+ links: {
+ github: 'https://github.com/technomancy-dev/00',
+ website: 'https://www.double-zero.cloud/',
+ docs: 'https://github.com/technomancy-dev/00',
+ },
+ tags: ['email'],
+ load: () => import('./doublezero/index').then((m) => m.generate),
}
];