rebrand to useSend (#210)
@@ -1,4 +1,4 @@
|
|||||||
DATABASE_URL="postgresql://unsend:password@localhost:54320/unsend"
|
DATABASE_URL="postgresql://usesend:password@localhost:54320/usesend"
|
||||||
REDIS_URL="redis://localhost:6379"
|
REDIS_URL="redis://localhost:6379"
|
||||||
|
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ AWS_SNS_ENDPOINT="http://localhost:3003/api/sns"
|
|||||||
|
|
||||||
NEXTAUTH_SECRET=""
|
NEXTAUTH_SECRET=""
|
||||||
|
|
||||||
FROM_EMAIL="hello@unsend.dev"
|
FROM_EMAIL="hello@usesend.com"
|
||||||
|
|
||||||
API_RATE_LIMIT=2
|
API_RATE_LIMIT=2
|
||||||
|
|
||||||
|
@@ -4,9 +4,9 @@ REDIS_URL="redis://redis:6379"
|
|||||||
# Postgres - required for docker-compose, not needed for just docker
|
# Postgres - required for docker-compose, not needed for just docker
|
||||||
POSTGRES_USER="postgres"
|
POSTGRES_USER="postgres"
|
||||||
POSTGRES_PASSWORD="postgres"
|
POSTGRES_PASSWORD="postgres"
|
||||||
POSTGRES_DB="unsend"
|
POSTGRES_DB="usesend"
|
||||||
# Postgres - required
|
# Postgres - required
|
||||||
DATABASE_URL="postgresql://postgres:postgres@postgres:5432/unsend"
|
DATABASE_URL="postgresql://postgres:postgres@postgres:5432/usesend"
|
||||||
|
|
||||||
# NextAuth - required
|
# NextAuth - required
|
||||||
NEXTAUTH_URL="http://localhost:3000"
|
NEXTAUTH_URL="http://localhost:3000"
|
||||||
@@ -14,7 +14,7 @@ NEXTAUTH_SECRET=
|
|||||||
|
|
||||||
#SMTP
|
#SMTP
|
||||||
SMTP_HOST=smtp.mailtrap.io # Example SMTP host
|
SMTP_HOST=smtp.mailtrap.io # Example SMTP host
|
||||||
SMTP_USER= "unsend" # Example SMTP user
|
SMTP_USER= "usesend" # Example SMTP user
|
||||||
|
|
||||||
## Auth providers any one is required
|
## Auth providers any one is required
|
||||||
# GitHub login - required
|
# GitHub login - required
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
/** @type {import("eslint").Linter.Config} */
|
/** @type {import("eslint").Linter.Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ignorePatterns: ["apps/**", "packages/**"],
|
ignorePatterns: ["apps/**", "packages/**"],
|
||||||
extends: ["@unsend/eslint-config/library.js"],
|
extends: ["@usesend/eslint-config/library.js"],
|
||||||
parser: "@typescript-eslint/parser",
|
parser: "@typescript-eslint/parser",
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: true,
|
project: true,
|
||||||
|
2
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -26,7 +26,7 @@ body:
|
|||||||
id: kind
|
id: kind
|
||||||
attributes:
|
attributes:
|
||||||
label: Self hosted or Cloud?
|
label: Self hosted or Cloud?
|
||||||
description: Does this happen on app.unsend.dev or on your own instance?
|
description: Does this happen on app.usesend.com or on your own instance?
|
||||||
options:
|
options:
|
||||||
- Cloud
|
- Cloud
|
||||||
- Self hosted
|
- Self hosted
|
||||||
|
70
.github/workflows/publish.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
- warp-ubuntu-latest-x64-2x
|
- warp-ubuntu-latest-x64-2x
|
||||||
- warp-ubuntu-latest-arm64-2x
|
- warp-ubuntu-latest-arm64-2x
|
||||||
app:
|
app:
|
||||||
- name: unsend
|
- name: usesend
|
||||||
dockerfile: ./docker/Dockerfile
|
dockerfile: ./docker/Dockerfile
|
||||||
context: .
|
context: .
|
||||||
- name: smtp-proxy
|
- name: smtp-proxy
|
||||||
@@ -65,16 +65,16 @@ jobs:
|
|||||||
docker build \
|
docker build \
|
||||||
-f "$DOCKER_FILE" \
|
-f "$DOCKER_FILE" \
|
||||||
--progress=plain \
|
--progress=plain \
|
||||||
-t "unsend/$APP-$BUILD_PLATFORM:latest" \
|
-t "usesend/$APP-$BUILD_PLATFORM:latest" \
|
||||||
-t "unsend/$APP-$BUILD_PLATFORM:$GIT_SHA" \
|
-t "usesend/$APP-$BUILD_PLATFORM:$GIT_SHA" \
|
||||||
-t "unsend/$APP-$BUILD_PLATFORM:$APP_VERSION" \
|
-t "usesend/$APP-$BUILD_PLATFORM:$APP_VERSION" \
|
||||||
-t "ghcr.io/unsend-dev/$APP-$BUILD_PLATFORM:latest" \
|
-t "ghcr.io/usesend/$APP-$BUILD_PLATFORM:latest" \
|
||||||
-t "ghcr.io/unsend-dev/$APP-$BUILD_PLATFORM:$GIT_SHA" \
|
-t "ghcr.io/usesend/$APP-$BUILD_PLATFORM:$GIT_SHA" \
|
||||||
-t "ghcr.io/unsend-dev/$APP-$BUILD_PLATFORM:$APP_VERSION" \
|
-t "ghcr.io/usesend/$APP-$BUILD_PLATFORM:$APP_VERSION" \
|
||||||
"$CONTEXT"
|
"$CONTEXT"
|
||||||
|
|
||||||
- name: Push the docker image to DockerHub
|
- name: Push the docker image to DockerHub
|
||||||
run: docker push --all-tags "unsend/$APP-$BUILD_PLATFORM"
|
run: docker push --all-tags "usesend/$APP-$BUILD_PLATFORM"
|
||||||
env:
|
env:
|
||||||
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-2x' && 'arm64' || 'amd64' }}
|
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-2x' && 'arm64' || 'amd64' }}
|
||||||
APP: ${{ matrix.app.name }}
|
APP: ${{ matrix.app.name }}
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
CONTEXT: ${{ matrix.app.context }}
|
CONTEXT: ${{ matrix.app.context }}
|
||||||
|
|
||||||
- name: Push the docker image to GitHub Container Registry
|
- name: Push the docker image to GitHub Container Registry
|
||||||
run: docker push --all-tags "ghcr.io/unsend-dev/$APP-$BUILD_PLATFORM"
|
run: docker push --all-tags "ghcr.io/usesend/$APP-$BUILD_PLATFORM"
|
||||||
env:
|
env:
|
||||||
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-2x' && 'arm64' || 'amd64' }}
|
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-2x' && 'arm64' || 'amd64' }}
|
||||||
APP: ${{ matrix.app.name }}
|
APP: ${{ matrix.app.name }}
|
||||||
@@ -117,25 +117,25 @@ jobs:
|
|||||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||||
GIT_SHA="$(git rev-parse HEAD)"
|
GIT_SHA="$(git rev-parse HEAD)"
|
||||||
|
|
||||||
for APP_NAME in unsend smtp-proxy; do
|
for APP_NAME in usesend smtp-proxy; do
|
||||||
docker manifest create \
|
docker manifest create \
|
||||||
unsend/$APP_NAME:latest \
|
usesend/$APP_NAME:latest \
|
||||||
--amend unsend/$APP_NAME-amd64:latest \
|
--amend usesend/$APP_NAME-amd64:latest \
|
||||||
--amend unsend/$APP_NAME-arm64:latest
|
--amend usesend/$APP_NAME-arm64:latest
|
||||||
|
|
||||||
docker manifest create \
|
docker manifest create \
|
||||||
unsend/$APP_NAME:$GIT_SHA \
|
usesend/$APP_NAME:$GIT_SHA \
|
||||||
--amend unsend/$APP_NAME-amd64:$GIT_SHA \
|
--amend usesend/$APP_NAME-amd64:$GIT_SHA \
|
||||||
--amend unsend/$APP_NAME-arm64:$GIT_SHA
|
--amend usesend/$APP_NAME-arm64:$GIT_SHA
|
||||||
|
|
||||||
docker manifest create \
|
docker manifest create \
|
||||||
unsend/$APP_NAME:$APP_VERSION \
|
usesend/$APP_NAME:$APP_VERSION \
|
||||||
--amend unsend/$APP_NAME-amd64:$APP_VERSION \
|
--amend usesend/$APP_NAME-amd64:$APP_VERSION \
|
||||||
--amend unsend/$APP_NAME-arm64:$APP_VERSION
|
--amend usesend/$APP_NAME-arm64:$APP_VERSION
|
||||||
|
|
||||||
docker manifest push unsend/$APP_NAME:latest
|
docker manifest push usesend/$APP_NAME:latest
|
||||||
docker manifest push unsend/$APP_NAME:$GIT_SHA
|
docker manifest push usesend/$APP_NAME:$GIT_SHA
|
||||||
docker manifest push unsend/$APP_NAME:$APP_VERSION
|
docker manifest push usesend/$APP_NAME:$APP_VERSION
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: Create and push GitHub Container Registry manifest
|
- name: Create and push GitHub Container Registry manifest
|
||||||
@@ -143,23 +143,23 @@ jobs:
|
|||||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||||
GIT_SHA="$(git rev-parse HEAD)"
|
GIT_SHA="$(git rev-parse HEAD)"
|
||||||
|
|
||||||
for APP_NAME in unsend smtp-proxy; do
|
for APP_NAME in usesend smtp-proxy; do
|
||||||
docker manifest create \
|
docker manifest create \
|
||||||
ghcr.io/unsend-dev/$APP_NAME:latest \
|
ghcr.io/usesend/$APP_NAME:latest \
|
||||||
--amend ghcr.io/unsend-dev/$APP_NAME-amd64:latest \
|
--amend ghcr.io/usesend/$APP_NAME-amd64:latest \
|
||||||
--amend ghcr.io/unsend-dev/$APP_NAME-arm64:latest
|
--amend ghcr.io/usesend/$APP_NAME-arm64:latest
|
||||||
|
|
||||||
docker manifest create \
|
docker manifest create \
|
||||||
ghcr.io/unsend-dev/$APP_NAME:$GIT_SHA \
|
ghcr.io/usesend/$APP_NAME:$GIT_SHA \
|
||||||
--amend ghcr.io/unsend-dev/$APP_NAME-amd64:$GIT_SHA \
|
--amend ghcr.io/usesend/$APP_NAME-amd64:$GIT_SHA \
|
||||||
--amend ghcr.io/unsend-dev/$APP_NAME-arm64:$GIT_SHA
|
--amend ghcr.io/usesend/$APP_NAME-arm64:$GIT_SHA
|
||||||
|
|
||||||
docker manifest create \
|
docker manifest create \
|
||||||
ghcr.io/unsend-dev/$APP_NAME:$APP_VERSION \
|
ghcr.io/usesend/$APP_NAME:$APP_VERSION \
|
||||||
--amend ghcr.io/unsend-dev/$APP_NAME-amd64:$APP_VERSION \
|
--amend ghcr.io/usesend/$APP_NAME-amd64:$APP_VERSION \
|
||||||
--amend ghcr.io/unsend-dev/$APP_NAME-arm64:$APP_VERSION
|
--amend ghcr.io/usesend/$APP_NAME-arm64:$APP_VERSION
|
||||||
|
|
||||||
docker manifest push ghcr.io/unsend-dev/$APP_NAME:latest
|
docker manifest push ghcr.io/usesend/$APP_NAME:latest
|
||||||
docker manifest push ghcr.io/unsend-dev/$APP_NAME:$GIT_SHA
|
docker manifest push ghcr.io/usesend/$APP_NAME:$GIT_SHA
|
||||||
docker manifest push ghcr.io/unsend-dev/$APP_NAME:$APP_VERSION
|
docker manifest push ghcr.io/usesend/$APP_NAME:$APP_VERSION
|
||||||
done
|
done
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
## Coding Style & Naming Conventions
|
## Coding Style & Naming Conventions
|
||||||
|
|
||||||
- TypeScript-first; 2-space indent; semicolons enabled by Prettier.
|
- TypeScript-first; 2-space indent; semicolons enabled by Prettier.
|
||||||
- Linting: `@unsend/eslint-config`; run `pnpm lint` before PRs.
|
- Linting: `@usesend/eslint-config`; run `pnpm lint` before PRs.
|
||||||
- Formatting: Prettier 3; run `pnpm format`.
|
- Formatting: Prettier 3; run `pnpm format`.
|
||||||
- Files: React components PascalCase (e.g., `AppSideBar.tsx`); folders kebab/lowercase.
|
- Files: React components PascalCase (e.g., `AppSideBar.tsx`); folders kebab/lowercase.
|
||||||
- Paths (web): use alias `~/` for src imports (e.g., `import { x } from "~/utils/x"`).
|
- Paths (web): use alias `~/` for src imports (e.g., `import { x } from "~/utils/x"`).
|
||||||
|
53
CLAUDE.md
@@ -1,53 +0,0 @@
|
|||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
- **Build**: `pnpm build` (specific: `pnpm build:web`, `pnpm build:editor`, `pnpm build:marketing`)
|
|
||||||
- **Lint**: `pnpm lint`
|
|
||||||
- **Dev**: `pnpm dev` (or `pnpm d` for setup + dev)
|
|
||||||
- **DB**: `pnpm db:migrate-dev`, `pnpm db:studio`, `pnpm db:push`
|
|
||||||
- **Test**: Run single test with `pnpm test --filter=web -- -t "test name"`
|
|
||||||
- **Format**: `pnpm format`
|
|
||||||
|
|
||||||
## Code Style
|
|
||||||
- **Formatting**: Prettier with tailwind plugin
|
|
||||||
- **Imports**: Group by source (internal/external), alphabetize
|
|
||||||
- **TypeScript**: Strong typing, avoid `any`, use Zod for validation
|
|
||||||
- **Naming**: camelCase for variables/functions, PascalCase for components/classes
|
|
||||||
- **React**: Functional components with hooks, group related hooks
|
|
||||||
- **Component Structure**: Props at top, hooks next, helper functions, then JSX
|
|
||||||
- **Error Handling**: Use try/catch with specific error types
|
|
||||||
- **API**: Use tRPC for internal, Hono for public API endpoints
|
|
||||||
|
|
||||||
Follow Vercel style guides with strict TypeScript. Be thoughtful, write readable code over premature optimization.
|
|
||||||
|
|
||||||
## Architecture Overview
|
|
||||||
|
|
||||||
Unsend is an open-source email sending infrastructure built as a monorepo with the following structure:
|
|
||||||
|
|
||||||
### Core Applications
|
|
||||||
- **web** (`apps/web`): Main Next.js dashboard application with tRPC API, Prisma ORM, authentication
|
|
||||||
- **marketing** (`apps/marketing`): Marketing website built with Next.js
|
|
||||||
- **smtp-server** (`apps/smtp-server`): SMTP server implementation
|
|
||||||
- **docs** (`apps/docs`): Documentation using Mintlify
|
|
||||||
|
|
||||||
### Shared Packages
|
|
||||||
- **email-editor** (`packages/email-editor`): Rich email editor using TipTap, JSX Email
|
|
||||||
- **ui** (`packages/ui`): Shared UI components using shadcn/ui and Tailwind
|
|
||||||
- **sdk** (`packages/sdk`): Client SDK for Unsend API
|
|
||||||
- **eslint-config**, **typescript-config**, **tailwind-config**: Shared configurations
|
|
||||||
|
|
||||||
### Key Technologies
|
|
||||||
- **Frontend**: Next.js 15, React 19, Tailwind CSS, shadcn/ui, Framer Motion
|
|
||||||
- **Backend**: tRPC, Prisma, PostgreSQL, Redis (BullMQ queues)
|
|
||||||
- **Email**: AWS SES, JSX Email, custom email editor
|
|
||||||
- **Auth**: NextAuth.js with GitHub/Google providers
|
|
||||||
- **API**: Hono for public REST API with OpenAPI/Swagger
|
|
||||||
- **Infrastructure**: Docker, Railway deployment ready
|
|
||||||
|
|
||||||
### Development Workflow
|
|
||||||
- Uses Turbo for monorepo builds and development
|
|
||||||
- Environment setup with `pnpm dx` (installs deps, starts Docker, runs migrations)
|
|
||||||
- Database operations prefixed with `pnpm db:`
|
|
||||||
- Each package has independent linting and building
|
|
@@ -1,6 +1,6 @@
|
|||||||
# 🤝 Contributing to Unsend
|
# 🤝 Contributing to useSend
|
||||||
|
|
||||||
Thanks for your interest in contributing to **Unsend**! We’re an open-source email infrastructure platform, and we’d love your help to make it even better. This guide will walk you through how to get started, set up the project locally, and submit contributions.
|
Thanks for your interest in contributing to **useSend**! We’re an open-source email infrastructure platform, and we’d love your help to make it even better. This guide will walk you through how to get started, set up the project locally, and submit contributions.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ Thanks for your interest in contributing to **Unsend**! We’re an open-source e
|
|||||||
All contributions begin with setting up the project locally. Follow the steps below to get started.
|
All contributions begin with setting up the project locally. Follow the steps below to get started.
|
||||||
|
|
||||||
📖 **Refer to the full setup guide:**
|
📖 **Refer to the full setup guide:**
|
||||||
[https://docs.unsend.dev/get-started/local](https://docs.unsend.dev/get-started/local)
|
[https://docs.usesend.com/get-started/local](https://docs.usesend.com/get-started/local)
|
||||||
|
|
||||||
### ⚙️ Prerequisites
|
### ⚙️ Prerequisites
|
||||||
|
|
||||||
@@ -28,8 +28,8 @@ You’ll need:
|
|||||||
### 1. Fork & Clone
|
### 1. Fork & Clone
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/YOUR-USERNAME/unsend.git
|
git clone https://github.com/YOUR-USERNAME/usesend.git
|
||||||
cd unsend
|
cd usesend
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Install Dependencies
|
### 2. Install Dependencies
|
||||||
@@ -58,6 +58,7 @@ openssl rand -base64 32
|
|||||||
### 4. GitHub OAuth (Optional for Dev)
|
### 4. GitHub OAuth (Optional for Dev)
|
||||||
|
|
||||||
Set up a GitHub OAuth App:
|
Set up a GitHub OAuth App:
|
||||||
|
|
||||||
- Homepage: `http://localhost:3000/login`
|
- Homepage: `http://localhost:3000/login`
|
||||||
- Callback: `http://localhost:3000/api/auth/callback/github`
|
- Callback: `http://localhost:3000/api/auth/callback/github`
|
||||||
|
|
||||||
@@ -147,7 +148,7 @@ apps/
|
|||||||
|
|
||||||
packages/
|
packages/
|
||||||
├── eslint-config # Shared ESLint rules
|
├── eslint-config # Shared ESLint rules
|
||||||
├── sdk # TypeScript SDK for Unsend REST API
|
├── sdk # TypeScript SDK for useSend REST API
|
||||||
├── tailwind-config # Shared Tailwind setup
|
├── tailwind-config # Shared Tailwind setup
|
||||||
├── typescript-config # Shared tsconfig
|
├── typescript-config # Shared tsconfig
|
||||||
├── ui # Shared UI components (buttons, modals, etc.)
|
├── ui # Shared UI components (buttons, modals, etc.)
|
||||||
@@ -164,6 +165,7 @@ git checkout -b feat/your-feature
|
|||||||
```
|
```
|
||||||
|
|
||||||
2. **Make Your Changes**
|
2. **Make Your Changes**
|
||||||
|
|
||||||
- Follow the existing project structure.
|
- Follow the existing project structure.
|
||||||
- Write clean, modular, and reusable code.
|
- Write clean, modular, and reusable code.
|
||||||
- Formatting is enforced with Prettier.
|
- Formatting is enforced with Prettier.
|
||||||
@@ -189,8 +191,8 @@ git push origin feat/your-feature
|
|||||||
## 💬 Community and Support
|
## 💬 Community and Support
|
||||||
|
|
||||||
- **Discord**: [Join our server](https://discord.gg/BU8n8pJv8S)
|
- **Discord**: [Join our server](https://discord.gg/BU8n8pJv8S)
|
||||||
- **GitHub Discussions**: [Start a discussion](https://github.com/unsend-dev/unsend/discussions)
|
- **GitHub Discussions**: [Start a discussion](https://github.com/usesend/usesend/discussions)
|
||||||
- **GitHub Issues**: [Report issues or bugs](https://github.com/unsend-dev/unsend/issues)
|
- **GitHub Issues**: [Report issues or bugs](https://github.com/usesend/usesend/issues)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -199,6 +201,6 @@ git push origin feat/your-feature
|
|||||||
Need help or unsure where to begin? Just ask!
|
Need help or unsure where to begin? Just ask!
|
||||||
|
|
||||||
- Chat with us on [Discord](https://discord.gg/BU8n8pJv8S)
|
- Chat with us on [Discord](https://discord.gg/BU8n8pJv8S)
|
||||||
- Open an [Issue](https://github.com/unsend-dev/unsend/issues)
|
- Open an [Issue](https://github.com/usesend/usesend/issues)
|
||||||
|
|
||||||
We’re excited to see your ideas and contributions! 💌
|
We’re excited to see your ideas and contributions! 💌
|
||||||
|
52
README.md
@@ -1,36 +1,36 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img style="width: 200px;height: 200px; margin: auto;" src="https://github.com/unsend-dev/unsend/assets/24666922/76268b21-0786-4f89-aa0f-e003fd0a6d60" alt="Unsend Logo">
|
<img style="width: 200px;height: 200px; margin: auto;" src="https://github.com/usesend/usesend/assets/24666922/76268b21-0786-4f89-aa0f-e003fd0a6d60" alt="useSend Logo">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center" style="margin-top: 20px">
|
<p align="center" style="margin-top: 20px">
|
||||||
<p align="center">
|
<p align="center">
|
||||||
The Open Source sending infrastructure.
|
The Open Source sending infrastructure.
|
||||||
<br>
|
<br>
|
||||||
<a href="https://unsend.dev"><strong>Learn more »</strong></a>
|
<a href="https://usesend.com"><strong>Learn more »</strong></a>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<a href="https://discord.gg/BU8n8pJv8S">Discord</a>
|
<a href="https://discord.gg/BU8n8pJv8S">Discord</a>
|
||||||
.
|
.
|
||||||
<a href="https://unsend.dev">Website</a>
|
<a href="https://usesend.com">Website</a>
|
||||||
·
|
·
|
||||||
<a href="https://github.com/unsend-dev/unsend/issues">Issues</a>
|
<a href="https://github.com/usesend/usesend/issues">Issues</a>
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://discord.gg/BU8n8pJv8S"><img src="https://img.shields.io/badge/Discord-unsend-%235865F2" alt="Join Unsend on Discord"></a>
|
<a href="https://discord.gg/BU8n8pJv8S"><img src="https://img.shields.io/badge/Discord-usesend-%235865F2" alt="Join useSend on Discord"></a>
|
||||||
<a href="https://github.com/unsend-dev/unsend/stargazers"><img src="https://img.shields.io/github/stars/unsend-dev%2Funsend" alt="GitHub Stars"></a>
|
<a href="https://github.com/usesend/usesend/stargazers"><img src="https://img.shields.io/github/stars/usesend%2Fusesend" alt="GitHub Stars"></a>
|
||||||
<a href="https://github.com/unsend-dev/unsend/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-AGPLv3-purple" alt="License"></a>
|
<a href="https://github.com/usesend/usesend/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-AGPLv3-purple" alt="License"></a>
|
||||||
<a href="https://hub.docker.com/r/unsend/unsend"><img alt="Docker Automated build" src="https://img.shields.io/docker/pulls/unsend/unsend"></a>
|
<a href="https://hub.docker.com/r/usesend/usesend"><img alt="Docker Automated build" src="https://img.shields.io/docker/pulls/usesend/usesend"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="https://github.com/unsend-dev/unsend/assets/24666922/68c41a6f-8fd1-4a3e-8d9b-987dda105c22" style="width: 100%;" />
|
<img src="https://github.com/usesend/usesend/assets/24666922/68c41a6f-8fd1-4a3e-8d9b-987dda105c22" style="width: 100%;" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## About this project
|
## About this project
|
||||||
|
|
||||||
As most of email products out there, Unsend also uses Amazon SES under the hood to send emails. We provide an open and alternative way to send emails reliably and cheaply with a great dashboard. You can also use Unsend manage contacts and send bulk emails(newsletter, product updates etc). We will take care of the subscriptions.
|
As most of email products out there, useSend also uses Amazon SES under the hood to send emails. We provide an open and alternative way to send emails reliably and cheaply with a great dashboard. You can also use useSend manage contacts and send bulk emails(newsletter, product updates etc). We will take care of the subscriptions.
|
||||||
|
|
||||||
Currently we only support emails, but we plan to expand to other sending protocols like SMS, push notification and even whatsapp.
|
Currently we only support emails, but we plan to expand to other sending protocols like SMS, push notification and even whatsapp.
|
||||||
|
|
||||||
@@ -50,14 +50,14 @@ We are currently in beta and trying to rollout to public slowly. If you're inter
|
|||||||
|
|
||||||
## Community and Next Steps 🎯
|
## Community and Next Steps 🎯
|
||||||
|
|
||||||
We're currently working on opening unsend for public beta.
|
We're currently working on opening useSend for public beta.
|
||||||
|
|
||||||
- Check out the first source code release in this repository and test it.
|
- Check out the first source code release in this repository and test it.
|
||||||
- Tell us what you think in the [Discussions](https://github.com/unsend-dev/unsend/discussions).
|
- Tell us what you think in the [Discussions](https://github.com/usesend/usesend/discussions).
|
||||||
- Join the [Discord server](https://discord.gg/BU8n8pJv8S) for any questions and getting to know to other community members.
|
- Join the [Discord server](https://discord.gg/BU8n8pJv8S) for any questions and getting to know to other community members.
|
||||||
- ⭐ the repository to help us raise awareness.
|
- ⭐ the repository to help us raise awareness.
|
||||||
- Spread the word on Twitter.
|
- Spread the word on Twitter.
|
||||||
- Fix or create [issues](https://github.com/unsend/unsend/issues), that are needed for the first production release.
|
- Fix or create [issues](https://github.com/usesend/usesend/issues), that are needed for the first production release.
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
@@ -72,25 +72,25 @@ We're currently working on opening unsend for public beta.
|
|||||||
|
|
||||||
### Email editor
|
### Email editor
|
||||||
|
|
||||||
Check out the editor code for [here](https://github.com/unsend-dev/unsend/tree/main/packages/email-editor). Editor is possible only because of the amazing tools and libraries.
|
Check out the editor code for [here](https://github.com/usesend/usesend/tree/main/packages/email-editor). Editor is possible only because of the amazing tools and libraries.
|
||||||
|
|
||||||
- [jsx-email](https://jsx.email/) - converts editor content to html
|
- [jsx-email](https://jsx.email/) - converts editor content to html
|
||||||
- [maily.to](https://maily.to/) - unsend email editor is greatly inspired from maily.to
|
- [maily.to](https://maily.to/) - useSend email editor is greatly inspired from maily.to
|
||||||
- [tiptap](https://tiptap.dev/) - editor core
|
- [tiptap](https://tiptap.dev/) - editor core
|
||||||
|
|
||||||
## Local Development
|
## Local Development
|
||||||
|
|
||||||
Follow our detailed guide to run Unsend locally
|
Follow our detailed guide to run useSend locally
|
||||||
|
|
||||||
[https://docs.unsend.dev/get-started/local](https://docs.unsend.dev/get-started/local)
|
[https://usesend.com/docs/get-started/local](https://usesend.com/docs/get-started/local)
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
We provide a Docker container for Unsend, which is published on both DockerHub and GitHub Container Registry.
|
We provide a Docker container for useSend, which is published on both DockerHub and GitHub Container Registry.
|
||||||
|
|
||||||
DockerHub: [https://hub.docker.com/r/unsend/unsend](https://hub.docker.com/r/unsend/unsend)
|
DockerHub: [https://hub.docker.com/r/usesend/usesend](https://hub.docker.com/r/usesend/usesend)
|
||||||
|
|
||||||
GitHub Container Registry: [https://ghcr.io/unsend-dev/unsend](https://ghcr.io/unsend-dev/unsend)
|
GitHub Container Registry: [https://ghcr.io/usesend/usesend](https://ghcr.io/usesend/usesend)
|
||||||
|
|
||||||
You can pull the Docker image from either of these registries and run it with your preferred container hosting provider.
|
You can pull the Docker image from either of these registries and run it with your preferred container hosting provider.
|
||||||
|
|
||||||
@@ -100,24 +100,22 @@ For detailed instructions on how to configure and run the Docker container, plea
|
|||||||
|
|
||||||
## Self Hosting
|
## Self Hosting
|
||||||
|
|
||||||
Checkout the [Self hosting](https://docs.unsend.dev/get-started/self-hosting) guide to learn how to self-host Unsend.
|
Checkout the [Self hosting](https://usesend.com/docs/get-started/self-hosting) guide to learn how to self-host useSend.
|
||||||
|
|
||||||
Also
|
Also
|
||||||
|
|
||||||
[](https://railway.app/template/QbMnwX?referralCode=oaAwvp)
|
[](https://railway.app/template/QbMnwX?referralCode=oaAwvp)
|
||||||
|
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
<a href="https://star-history.com/#unsend-dev/unsend&Date">
|
<a href="https://star-history.com/#usesend/usesend&Date">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=unsend-dev/unsend&type=Date&theme=dark" />
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=usesend/usesend&type=Date&theme=dark" />
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=unsend-dev/unsend&type=Date" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=usesend/usesend&type=Date" />
|
||||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=unsend-dev/unsend&type=Date" />
|
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=usesend/usesend&type=Date" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
We are grateful for the support of our sponsors.
|
We are grateful for the support of our sponsors.
|
||||||
|
@@ -5,12 +5,12 @@ description: "Fundamental concepts of Usend's API."
|
|||||||
|
|
||||||
## Base URL
|
## Base URL
|
||||||
|
|
||||||
Unsend's API is built on REST principles and is served over HTTPS. To ensure data privacy, unencrypted HTTP is not supported.
|
useSend's API is built on REST principles and is served over HTTPS. To ensure data privacy, unencrypted HTTP is not supported.
|
||||||
|
|
||||||
The Base URL for all API endpoints is:
|
The Base URL for all API endpoints is:
|
||||||
|
|
||||||
```sh Terminal
|
```sh Terminal
|
||||||
https://app.unsend.dev/api/
|
https://app.usesend.com/api/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
@@ -21,4 +21,4 @@ Authentication to Usend's API is performed via the Authorization header with a B
|
|||||||
Authorization: Bearer us_12345
|
Authorization: Bearer us_12345
|
||||||
```
|
```
|
||||||
|
|
||||||
You can create a new token/API key under your Unsend [Developer Settings](https://app.unsend.dev/dev-settings/api-keys).
|
You can create a new token/API key under your useSend [Developer Settings](https://app.usesend.com/dev-settings/api-keys).
|
||||||
|
@@ -2,11 +2,11 @@
|
|||||||
"openapi": "3.0.0",
|
"openapi": "3.0.0",
|
||||||
"info": {
|
"info": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"title": "Unsend API"
|
"title": "useSend API"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"url": "https://app.unsend.dev/api"
|
"url": "https://app.usesend.com/api"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"components": {
|
"components": {
|
||||||
|
@@ -57,9 +57,9 @@ func main() {
|
|||||||
request := &unsend.SendEmailRequest{
|
request := &unsend.SendEmailRequest{
|
||||||
To: []string{"youremail@gmail.com"},
|
To: []string{"youremail@gmail.com"},
|
||||||
From: "hello@yourdomain.com",
|
From: "hello@yourdomain.com",
|
||||||
Subject: "Unsend test email",
|
Subject: "Unsend test email",
|
||||||
Text: "hello,\n\nUnsend is the best open source sending platform",
|
Text: "hello,\n\nUnsend is the best open source sending platform",
|
||||||
Html: "<p>hello,</p><p>Unsend is the best open source sending platform</p><p>check out <a href='https://unsend.dev'>unsend.dev</a></p>",
|
Html: "<p>hello,</p><p>Unsend is the best open source sending platform</p><p>check out <a href='https://unsend.dev'>unsend.dev</a></p>",
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := client.Emails.SendEmail(context.Background(), *request)
|
response, err := client.Emails.SendEmail(context.Background(), *request)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Create AWS credentials
|
title: Create AWS credentials
|
||||||
description: Step by step guide to create AWS credentials to self-host Unsend.
|
description: Step by step guide to create AWS credentials to self-host useSend.
|
||||||
---
|
---
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
title: Local Development
|
title: Local Development
|
||||||
description: "A guide on how to run Unsend’s codebase locally"
|
description: "A guide on how to run useSend’s codebase locally"
|
||||||
icon: code
|
icon: code
|
||||||
---
|
---
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Unsend's codebase is fully [open-source on github](https://github.com/unsend-dev/unsend)
|
useSend's codebase is fully [open-source on github](https://github.com/usesend/usesend)
|
||||||
|
|
||||||
Here is the codebase structure
|
Here is the codebase structure
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ The `apps` directory contains the code for:
|
|||||||
|
|
||||||
- `web`: Code for our dashboard and email infra
|
- `web`: Code for our dashboard and email infra
|
||||||
|
|
||||||
- `marketing`: The code for the landing page of Unsend
|
- `marketing`: The code for the landing page of useSend
|
||||||
|
|
||||||
- `docs`: The documentation that you are currently reading.
|
- `docs`: The documentation that you are currently reading.
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ The `packages` directory contains the code for:
|
|||||||
|
|
||||||
- `eslint-config` package contains shared ESLint configuration settings
|
- `eslint-config` package contains shared ESLint configuration settings
|
||||||
|
|
||||||
- `sdk` package contains typescript sdk for unsend rest api
|
- `sdk` package contains TypeScript SDK for useSend REST API
|
||||||
|
|
||||||
- `tailwind-config` This package contains a shared Tailwind CSS configuration.
|
- `tailwind-config` This package contains a shared Tailwind CSS configuration.
|
||||||
|
|
||||||
@@ -43,9 +43,9 @@ The `packages` directory contains the code for:
|
|||||||
|
|
||||||
- `ui` This package is a collection of reusable UI components like buttons, badges, etc
|
- `ui` This package is a collection of reusable UI components like buttons, badges, etc
|
||||||
|
|
||||||
## Running Unsend locally
|
## Running useSend locally
|
||||||
|
|
||||||
To run Unsend, locally you will need to setup the following:
|
To run useSend locally, you will need to setup the following:
|
||||||
|
|
||||||
- [AWS](https://aws.amazon.com/) Free tier account will work.
|
- [AWS](https://aws.amazon.com/) Free tier account will work.
|
||||||
|
|
||||||
@@ -57,13 +57,13 @@ To run Unsend, locally you will need to setup the following:
|
|||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
<Step title="Fork the repo">
|
<Step title="Fork the repo">
|
||||||
Click on the fork button on [GitHub](https://github.com/unsend-dev/unsend) to fork the repo
|
Click on the fork button on [GitHub](https://github.com/usesend/usesend) to fork the repo
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Clone the repo">
|
<Step title="Clone the repo">
|
||||||
Once the repo is forked you can clone it on your local machine using:
|
Once the repo is forked you can clone it on your local machine using:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/your-username/unsend.git
|
git clone https://github.com/your-username/usesend.git
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
@@ -107,7 +107,7 @@ openssl rand -base64 32
|
|||||||
variables. for development email link will logged in the console.
|
variables. for development email link will logged in the console.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
Next, [create a new GitHub App](https://github.com/settings/applications/new). This will allow you to sign in to Unsend with your GitHub account.4
|
Next, [create a new GitHub App](https://github.com/settings/applications/new). This will allow you to sign in to useSend with your GitHub account.
|
||||||
|
|
||||||
Add the homepage as:
|
Add the homepage as:
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ Once the app is added you can add the Client ID under `GITHUB_ID`and CLIENT SECR
|
|||||||
will not be sent out.
|
will not be sent out.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
Next, we need to add in the [AWS credentials](https://docs.unsend.dev/get-started/create-aws-credentials). Follow the detailed guide to get the AWS credentials with accurate permissions and add them in:
|
Next, we need to add in the [AWS credentials](https://docs.usesend.com/get-started/create-aws-credentials). Follow the detailed guide to get the AWS credentials with accurate permissions and add them in:
|
||||||
|
|
||||||
```
|
```
|
||||||
AWS_ACCESS_KEY=<access-key-id>
|
AWS_ACCESS_KEY=<access-key-id>
|
||||||
@@ -141,7 +141,7 @@ AWS_SECRET_KEY=<secret-access-key>
|
|||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
## Running Unsend locally
|
## Running useSend locally
|
||||||
|
|
||||||
We are using a local Postgresql server and a local Redis server. But if you don't have docker you can also manually set these up.
|
We are using a local Postgresql server and a local Redis server. But if you don't have docker you can also manually set these up.
|
||||||
|
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
---
|
---
|
||||||
title: NodeJS
|
title: NodeJS
|
||||||
description: "Send your mail using unsend in NodeJS"
|
description: "Send your mail using useSend in NodeJS"
|
||||||
icon: node-js
|
icon: node-js
|
||||||
---
|
---
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- [Unsend API key](https://app.unsend.dev/dev-settings/api-keys)
|
- [useSend API key](https://app.usesend.com/dev-settings/api-keys)
|
||||||
- [Verified domain](https://app.unsend.dev/domains)
|
- [Verified domain](https://app.usesend.com/domains)
|
||||||
|
|
||||||
## Using SDK
|
## Using SDK
|
||||||
|
|
||||||
@@ -15,48 +15,48 @@ icon: node-js
|
|||||||
<Step title="Install SDK">
|
<Step title="Install SDK">
|
||||||
<CodeGroup>
|
<CodeGroup>
|
||||||
```bash npm
|
```bash npm
|
||||||
npm install unsend
|
npm install usesend
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash yarn
|
```bash yarn
|
||||||
yarn add unsend
|
yarn add usesend
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash pnpm
|
```bash pnpm
|
||||||
pnpm add unsend
|
pnpm add usesend
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash bun
|
```bash bun
|
||||||
bun add unsend
|
bun add usesend
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Initialize SDK">
|
<Step title="Initialize SDK">
|
||||||
Get the API key from the [Unsend dashboard](https://app.unsend.dev/dev-settings/api-keys) and initialize the SDK
|
Get the API key from the [useSend dashboard](https://app.usesend.com/dev-settings/api-keys) and initialize the SDK
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import { Unsend } from "unsend";
|
import { UseSend } from "usesend";
|
||||||
|
|
||||||
const unsend = new Unsend("us_12345");
|
const usesend = new UseSend("us_12345");
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are running a self-hosted version of Unsend, pass the base URL as the
|
If you are running a self-hosted version of useSend, pass the base URL as the
|
||||||
second argument:
|
second argument:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const unsend = new Unsend("us_12345", "https://my-unsend-instance.com");
|
const usesend = new UseSend("us_12345", "https://app.usesend.com");
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Send Email">
|
<Step title="Send Email">
|
||||||
```javascript
|
```javascript
|
||||||
unsend.emails.send({
|
usesend.emails.send({
|
||||||
to: "hello@acme.com",
|
to: "hello@acme.com",
|
||||||
from: "hello@company.com",
|
from: "hello@company.com",
|
||||||
subject: "Unsend email",
|
subject: "useSend email",
|
||||||
html: "<p>Unsend is the best open source product to send emails</p>",
|
html: "<p>useSend is the best open source product to send emails</p>",
|
||||||
text: "Unsend is the best open source product to send emails",
|
text: "useSend is the best open source product to send emails",
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
</Step>
|
</Step>
|
||||||
@@ -66,12 +66,12 @@ icon: node-js
|
|||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
<Step title="Get the contact book id">
|
<Step title="Get the contact book id">
|
||||||
Get the contact book id from the [Unsend dashboard](https://app.unsend.dev/contacts/). Copy the contact book id
|
Get the contact book id from the [useSend dashboard](https://app.usesend.com/contacts/). Copy the contact book id
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Add contacts">
|
<Step title="Add contacts">
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
unsend.contacts
|
usesend.contacts
|
||||||
.create("clzeydgeygff", {
|
.create("clzeydgeygff", {
|
||||||
email: "hey@koushik.dev",
|
email: "hey@koushik.dev",
|
||||||
firstName: "Koushik",
|
firstName: "Koushik",
|
||||||
@@ -83,7 +83,7 @@ icon: node-js
|
|||||||
|
|
||||||
<Step title="Update contact">
|
<Step title="Update contact">
|
||||||
```javascript
|
```javascript
|
||||||
unsend.contacts.update("clzeydgeygff", contactId, {
|
usesend.contacts.update("clzeydgeygff", contactId, {
|
||||||
firstName: "Koushik",
|
firstName: "Koushik",
|
||||||
lastName: "KM",
|
lastName: "KM",
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Self hosting Unsend
|
title: Self hosting useSend
|
||||||
description: "An end-to-end guide on how to self-host Unsend. An opensource sending infrastructure for developers."
|
description: "An end-to-end guide on how to self-host useSend. An open-source sending infrastructure for developers."
|
||||||
icon: server
|
icon: server
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ If you have any questions join [#self-host](https://discord.gg/gbsvjb9MqV) on di
|
|||||||
|
|
||||||
## Step 1: Environment variables
|
## Step 1: Environment variables
|
||||||
|
|
||||||
Unsend depends on AWS ses to send emails and SNS to receive email status. Along with that it also depends on Postgres as a database and Redis for queue. Copy the `.env.selfhost.example` file to `.env` and fill in the values.
|
useSend depends on AWS SES to send emails and SNS to receive email status. Along with that it also depends on Postgres as a database and Redis for queue. Copy the `.env.selfhost.example` file to `.env` and fill in the values.
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
<Step title="AWS credentials">
|
<Step title="AWS credentials">
|
||||||
@@ -33,11 +33,11 @@ Add the following environment variables.
|
|||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="GitHub app credentials for login">
|
<Step title="GitHub app credentials for login">
|
||||||
Usend uses github authentication for login.
|
useSend uses GitHub authentication for login.
|
||||||
|
|
||||||
Use this link to [create an github app](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps)
|
Use this link to [create an github app](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps)
|
||||||
|
|
||||||
Callback URL : `https://<your-unsend-instance>/api/auth/callback/github`
|
Callback URL : `https://<your-usesend-instance>/api/auth/callback/github`
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -48,10 +48,10 @@ GITHUB_ID="<your-github-client-id>"
|
|||||||
GITHUB_SECRET="<your-github-client-secret>"
|
GITHUB_SECRET="<your-github-client-secret>"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Info>If you want email/password login, please help us out with the [code](https://github.com/unsend-dev/unsend) </Info>
|
<Info>If you want email/password login, please help us out with the [code](https://github.com/usesend/usesend) </Info>
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Database & Redis">
|
<Step title="Database & Redis">
|
||||||
Unsend uses Postgres as a database and Redis as a queue. You need to create a new database and add the following environment variables.
|
useSend uses Postgres as a database and Redis as a queue. You need to create a new database and add the following environment variables.
|
||||||
|
|
||||||
If you're using docker-compose or our railway template, it's all automatically done for you.
|
If you're using docker-compose or our railway template, it's all automatically done for you.
|
||||||
|
|
||||||
@@ -71,8 +71,8 @@ openssl rand -base64 32
|
|||||||
Add the following environment variables.
|
Add the following environment variables.
|
||||||
|
|
||||||
```env
|
```env
|
||||||
NEXTAUTH_URL="https://<your-unsend-instance>"
|
NEXTAUTH_URL="https://<your-usesend-instance>"
|
||||||
NEXTAUTH_SECRET="<your-unsend-secret>"
|
NEXTAUTH_SECRET="<your-usesend-secret>"
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
@@ -86,7 +86,7 @@ You can use any platforms that supports docker. You can also use the railway tem
|
|||||||
|
|
||||||
Follow this guide to setup your docker instance: [Set up docker](/get-started/set-up-docker)
|
Follow this guide to setup your docker instance: [Set up docker](/get-started/set-up-docker)
|
||||||
|
|
||||||
[](https://hub.docker.com/r/unsend/unsend)
|
[](https://hub.docker.com/r/usesend/usesend)
|
||||||
|
|
||||||
### Railway
|
### Railway
|
||||||
|
|
||||||
@@ -96,15 +96,15 @@ Updating image is easy, click on the 3 dots and redeploy. This will pull the lat
|
|||||||
|
|
||||||
[](https://railway.app/template/QbMnwX?referralCode=oaAwvp)
|
[](https://railway.app/template/QbMnwX?referralCode=oaAwvp)
|
||||||
|
|
||||||
Your unsend instance is now live now.
|
Your useSend instance is now live now.
|
||||||
|
|
||||||
## Step 3: Setting up a region
|
## Step 3: Setting up a region
|
||||||
|
|
||||||
In order to send emails, you need to select an region in aws. Use a region where your users are located / where unsend is hosted. If you're confused just use `us-east-1`.
|
In order to send emails, you need to select a region in AWS. Use a region where your users are located / where useSend is hosted. If you're confused just use `us-east-1`.
|
||||||
|
|
||||||
You can check available regions [here](https://docs.aws.amazon.com/general/latest/gr/ses.html)
|
You can check available regions [here](https://docs.aws.amazon.com/general/latest/gr/ses.html)
|
||||||
|
|
||||||
Once you logged in to unsend, it will prompt you add ses configuration.
|
Once you log in to useSend, it will prompt you add SES configuration.
|
||||||
|
|
||||||
- Add the region
|
- Add the region
|
||||||
- Add the callback url, which is basically the app url. Note this should be accesible from internet. This is how you get the delivery status of the emails.
|
- Add the callback url, which is basically the app url. Note this should be accesible from internet. This is how you get the delivery status of the emails.
|
||||||
@@ -120,10 +120,10 @@ Once you logged in to unsend, it will prompt you add ses configuration.
|
|||||||
|
|
||||||
## Step 5: SMTP Proxy Server (Optional)
|
## Step 5: SMTP Proxy Server (Optional)
|
||||||
|
|
||||||
The SMTP proxy server is an optional component that allows applications to send emails through Unsend using standard SMTP protocol instead of the REST API. This is useful for legacy applications, email clients, or any software that needs to send emails via SMTP.
|
The SMTP proxy server is an optional component that allows applications to send emails through useSend using standard SMTP protocol instead of the REST API. This is useful for legacy applications, email clients, or any software that needs to send emails via SMTP.
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
The complete source code for the SMTP proxy server is available at: [unsend-dev/unsend/tree/main/apps/smtp-server](https://github.com/unsend-dev/unsend/tree/main/apps/smtp-server)
|
The complete source code for the SMTP proxy server is available at: [usesend/usesend/tree/main/apps/smtp-server](https://github.com/usesend/usesend/tree/main/apps/smtp-server)
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
### When to use the SMTP proxy:
|
### When to use the SMTP proxy:
|
||||||
@@ -137,19 +137,19 @@ The complete source code for the SMTP proxy server is available at: [unsend-dev/
|
|||||||
Create a `docker-compose.yml` file for the SMTP server:
|
Create a `docker-compose.yml` file for the SMTP server:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: unsend-smtp-server
|
name: usesend-smtp-server
|
||||||
|
|
||||||
services:
|
services:
|
||||||
smtp-server:
|
smtp-server:
|
||||||
container_name: unsend-smtp-server
|
container_name: usesend-smtp-server
|
||||||
image: unsend/smtp-proxy:latest
|
image: usesend/smtp-proxy:latest
|
||||||
environment:
|
environment:
|
||||||
SMTP_AUTH_USERNAME: "unsend" # Username for SMTP authentication
|
SMTP_AUTH_USERNAME: "usesend" # Username for SMTP authentication
|
||||||
UNSEND_BASE_URL: "https://your-unsend-instance.com" # Your Unsend instance URL
|
USESEND_BASE_URL: "https://your-usesend-instance.com" # Your useSend instance URL
|
||||||
|
|
||||||
# Optional: SSL certificate paths for secure connections
|
# Optional: SSL certificate paths for secure connections
|
||||||
# UNSEND_API_KEY_PATH: "/certs/server.key"
|
# USESEND_API_KEY_PATH: "/certs/server.key"
|
||||||
# UNSEND_API_CERT_PATH: "/certs/server.crt"
|
# USESEND_API_CERT_PATH: "/certs/server.crt"
|
||||||
|
|
||||||
# Optional: Mount SSL certificates
|
# Optional: Mount SSL certificates
|
||||||
# volumes:
|
# volumes:
|
||||||
@@ -178,12 +178,12 @@ To send emails through the proxy, configure your application with these SMTP set
|
|||||||
|
|
||||||
- **Host**: Your server's IP address or domain
|
- **Host**: Your server's IP address or domain
|
||||||
- **Ports**: 25, 587 (STARTTLS), 465 (SSL/TLS), 2587, or 2465
|
- **Ports**: 25, 587 (STARTTLS), 465 (SSL/TLS), 2587, or 2465
|
||||||
- **Username**: `unsend` (or your custom `SMTP_AUTH_USERNAME`)
|
- **Username**: `usesend` (or your custom `SMTP_AUTH_USERNAME`)
|
||||||
- **Password**: Your Unsend API key
|
- **Password**: Your useSend API key
|
||||||
- **Encryption**: STARTTLS (ports 25, 587, 2587) or SSL/TLS (ports 465, 2465)
|
- **Encryption**: STARTTLS (ports 25, 587, 2587) or SSL/TLS (ports 465, 2465)
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
The SMTP proxy forwards all emails to your Unsend instance, so make sure your main Unsend application is running and accessible.
|
The SMTP proxy forwards all emails to your useSend instance, so make sure your main useSend application is running and accessible.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
@@ -201,5 +201,5 @@ You're all set up now.
|
|||||||
If you have any questions, please join [#self-host](https://discord.gg/gbsvjb9MqV) on discord.
|
If you have any questions, please join [#self-host](https://discord.gg/gbsvjb9MqV) on discord.
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
A community member shared a short write-up on hosting Unsend with [Coolify](https://mattstein.com/thoughts/coolify-unsend/). Give it a read if you need another reference.
|
A community member shared a short write-up on hosting useSend with [Coolify](https://mattstein.com/thoughts/coolify-unsend/). Give it a read if you need another reference.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Docker setup for unsend
|
title: Docker setup for useSend
|
||||||
description: The following guide will walk you through setting up Unsend using Docker. You can choose between a production setup using Docker Compose or a standalone container.
|
description: The following guide will walk you through setting up useSend using Docker. You can choose between a production setup using Docker Compose or a standalone container.
|
||||||
---
|
---
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
@@ -12,9 +12,9 @@ Before you begin, ensure that you have the following installed:
|
|||||||
|
|
||||||
## Option 1: Production Docker Compose Setup
|
## Option 1: Production Docker Compose Setup
|
||||||
|
|
||||||
This setup includes PostgreSQL, Redis and the Unsend application.
|
This setup includes PostgreSQL, Redis and the useSend application.
|
||||||
|
|
||||||
1. Download the Docker Compose file from the Unsend repository: [compose.yml](https://github.com/unsend-dev/unsend/blob/main/docker/prod/compose.yml)
|
1. Download the Docker Compose file from the useSend repository: [compose.yml](https://github.com/usesend/usesend/blob/main/docker/prod/compose.yml)
|
||||||
2. Navigate to the directory containing the `compose.yml` file.
|
2. Navigate to the directory containing the `compose.yml` file.
|
||||||
3. Create a `.env` file in the same directory. Copy the contents of `.env.selfhost.example`
|
3. Create a `.env` file in the same directory. Copy the contents of `.env.selfhost.example`
|
||||||
4. Run the following command to start the containers:
|
4. Run the following command to start the containers:
|
||||||
@@ -23,24 +23,24 @@ This setup includes PostgreSQL, Redis and the Unsend application.
|
|||||||
docker-compose --env-file ./.env up -d
|
docker-compose --env-file ./.env up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
This will start the PostgreSQL database, Redis and the Unsend application containers.
|
This will start the PostgreSQL database, Redis and the useSend application containers.
|
||||||
|
|
||||||
5. Access the Unsend application by visiting `http://localhost:3000` in your web browser.
|
5. Access the useSend application by visiting `http://localhost:3000` in your web browser.
|
||||||
|
|
||||||
## Option 2: Standalone Docker Container
|
## Option 2: Standalone Docker Container
|
||||||
|
|
||||||
If you prefer to host the Unsend application on your container provider of choice, you can use the pre-built Docker image from DockerHub or GitHub's Package Registry. Note that you will need to provide your own database and SMTP host.
|
If you prefer to host the useSend application on your container provider of choice, you can use the pre-built Docker image from DockerHub or GitHub's Package Registry. Note that you will need to provide your own database and SMTP host.
|
||||||
|
|
||||||
1. Pull the Unsend Docker image:
|
1. Pull the useSend Docker image:
|
||||||
|
|
||||||
```
|
```
|
||||||
docker pull unsend/unsend
|
docker pull usesend/usesend
|
||||||
```
|
```
|
||||||
|
|
||||||
Or, if using GitHub's Package Registry:
|
Or, if using GitHub's Package Registry:
|
||||||
|
|
||||||
```
|
```
|
||||||
docker pull ghcr.io/unsend-dev/unsend
|
docker pull ghcr.io/usesend/usesend
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Run the Docker container, providing the necessary environment variables for your database and SMTP host:
|
2. Run the Docker container, providing the necessary environment variables for your database and SMTP host:
|
||||||
@@ -57,13 +57,13 @@ docker run -d \
|
|||||||
-e AWS_DEFAULT_REGION="<your-next-private-aws-region>"
|
-e AWS_DEFAULT_REGION="<your-next-private-aws-region>"
|
||||||
-e GITHUB_ID="<your-next-private-github-id>"
|
-e GITHUB_ID="<your-next-private-github-id>"
|
||||||
-e GITHUB_SECRET="<your-next-private-github-secret>"
|
-e GITHUB_SECRET="<your-next-private-github-secret>"
|
||||||
unsend/unsend
|
usesend/usesend
|
||||||
```
|
```
|
||||||
|
|
||||||
Replace the placeholders with your actual database and aws details.
|
Replace the placeholders with your actual database and aws details.
|
||||||
|
|
||||||
1. Access the Unsend application by visiting the URL you provided in the `NEXTAUTH_URL` environment variable in your web browser.
|
1. Access the useSend application by visiting the URL you provided in the `NEXTAUTH_URL` environment variable in your web browser.
|
||||||
|
|
||||||
## Success
|
## Success
|
||||||
|
|
||||||
You have now successfully set up Unsend using Docker. You can start sending emails efficiently. If you encounter any issues or have further questions, please refer to the official Unsend documentation or seek assistance from the community.
|
You have now successfully set up useSend using Docker. You can start sending emails efficiently. If you encounter any issues or have further questions, please refer to the official useSend documentation or seek assistance from the community.
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: SMTP support
|
title: SMTP support
|
||||||
description: "A guide to integrate Unsend with SMTP"
|
description: "A guide to integrate useSend with SMTP"
|
||||||
icon: envelope
|
icon: envelope
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -8,31 +8,31 @@ icon: envelope
|
|||||||
|
|
||||||
You will need an API key and a verified domain to get the most out of this guide:
|
You will need an API key and a verified domain to get the most out of this guide:
|
||||||
|
|
||||||
- [API Key](https://app.unsend.dev/dev-settings/api-keys)
|
- [API Key](https://app.usesend.com/dev-settings/api-keys)
|
||||||
- [Verified Domain](https://app.unsend.dev/domains)
|
- [Verified Domain](https://app.usesend.com/domains)
|
||||||
|
|
||||||
## SMTP credentials
|
## SMTP credentials
|
||||||
|
|
||||||
To set up your SMTP integration, you'll need to provide the following credentials:
|
To set up your SMTP integration, you'll need to provide the following credentials:
|
||||||
|
|
||||||
- **Host:** ```smtp.unsend.dev```
|
- **Host:** ```smtp.usesend.com```
|
||||||
- **Port:** ```465```, ```587```, ```2465```, or ```2587```
|
- **Port:** ```465```, ```587```, ```2465```, or ```2587```
|
||||||
- **Username:** ```unsend```
|
- **Username:** ```usesend```
|
||||||
- **Password:** ```YOUR-API-KEY```
|
- **Password:** ```YOUR-API-KEY```
|
||||||
|
|
||||||
## Example with Nodemailer
|
## Example with Nodemailer
|
||||||
|
|
||||||
Following example with Nodemailer shows how you can send mails with SMTP support from Unsend and Nodemailer.
|
Following example with Nodemailer shows how you can send mails with SMTP support from useSend and Nodemailer.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const nodemailer = require("nodemailer");
|
const nodemailer = require("nodemailer");
|
||||||
|
|
||||||
const transporter = nodemailer.createTransport({
|
const transporter = nodemailer.createTransport({
|
||||||
host: "smtp.unsend.dev",
|
host: "smtp.usesend.com",
|
||||||
port: 465,
|
port: 465,
|
||||||
secure: false,
|
secure: false,
|
||||||
auth: {
|
auth: {
|
||||||
user: "unsend",
|
user: "usesend",
|
||||||
pass: "us_123",
|
pass: "us_123",
|
||||||
},
|
},
|
||||||
tls: {
|
tls: {
|
||||||
@@ -44,8 +44,8 @@ const mailOptions = {
|
|||||||
to: "sender@example.com",
|
to: "sender@example.com",
|
||||||
from: "hello@example.com",
|
from: "hello@example.com",
|
||||||
subject: "Testing SMTP",
|
subject: "Testing SMTP",
|
||||||
html: "<strong>THIS IS USING SMTP,</strong><p>Unsend is the best open source sending platform<p><p>check out <a href='https://unsend.dev'>unsend.dev</a>",
|
html: "<strong>THIS IS USING SMTP,</strong><p>useSend is the best open source sending platform<p><p>check out <a href='https://usesend.com'>usesend.com</a>",
|
||||||
text: "hello,\n\nUnsend is the best open source sending platform",
|
text: "hello,\n\nuseSend is the best open source sending platform",
|
||||||
};
|
};
|
||||||
|
|
||||||
transporter.sendMail(mailOptions, (error, info) => {
|
transporter.sendMail(mailOptions, (error, info) => {
|
||||||
|
@@ -1,29 +1,29 @@
|
|||||||
---
|
---
|
||||||
title: Use with React Email
|
title: Use with React Email
|
||||||
description: "A guide on how to use Unsend with React Email"
|
description: "A guide on how to use useSend with React Email"
|
||||||
---
|
---
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
[React Email](https://react.email/docs/introduction) is a library for building emails with React. In this guide, we will show you how to use Unsend with React Email.
|
[React Email](https://react.email/docs/introduction) is a library for building emails with React. In this guide, we will show you how to use useSend with React Email.
|
||||||
|
|
||||||
## Install dependencies
|
## Install dependencies
|
||||||
|
|
||||||
<CodeGroup>
|
<CodeGroup>
|
||||||
```sh npm
|
```sh npm
|
||||||
npm install unsend @react-email/render
|
npm install usesend @react-email/render
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh yarn
|
```sh yarn
|
||||||
yarn add unsend @react-email/render
|
yarn add usesend @react-email/render
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh pnpm
|
```sh pnpm
|
||||||
pnpm add unsend @react-email/render
|
pnpm add usesend @react-email/render
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh bun
|
```sh bun
|
||||||
bun add unsend @react-email/render
|
bun add usesend @react-email/render
|
||||||
```
|
```
|
||||||
|
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
@@ -46,21 +46,21 @@ export function Email(props) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Send an email using Unsend
|
## Send an email using useSend
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { Unsend } from "unsend";
|
import { UseSend } from "usesend";
|
||||||
import { render } from "@react-email/render";
|
import { render } from "@react-email/render";
|
||||||
import { Email } from "./email";
|
import { Email } from "./email";
|
||||||
|
|
||||||
const unsend = new Unsend("us_your_unsend_api_key");
|
const usesend = new UseSend("us_your_usesend_api_key");
|
||||||
|
|
||||||
const html = await render(<Email url="https://unsend.dev" />);
|
const html = await render(<Email url="https://usesend.com" />);
|
||||||
|
|
||||||
const response = await unsend.emails.send({
|
const response = await usesend.emails.send({
|
||||||
to: "hello@unsend.dev",
|
to: "hello@usesend.com",
|
||||||
from: "hello@unsend.dev",
|
from: "hello@usesend.com",
|
||||||
subject: "Unsend email",
|
subject: "useSend email",
|
||||||
html,
|
html,
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
@@ -77,7 +77,7 @@ If you're using nodejs, importing `email.jsx` might fail. make sure to add these
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Checkout this [example](https://github.com/unsend-dev/unsend-js-examples/tree/main/react-email-js)
|
Checkout this [example](https://github.com/usesend/unsend-js-examples/tree/main/react-email-js)
|
||||||
|
|
||||||
### TypeScript
|
### TypeScript
|
||||||
|
|
||||||
@@ -89,4 +89,4 @@ Just add `jsx` to your `tsconfig.json`
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Checkout this [example](https://github.com/unsend-dev/unsend-js-examples/tree/main/react-email-ts)
|
Checkout this [example](https://github.com/usesend/unsend-js-examples/tree/main/react-email-ts)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Introduction
|
title: Introduction
|
||||||
description: "Unsend is Open source alternative to Resend, Sendgrid, Mailgun and Postmark etc."
|
description: "useSend is open source alternative to Resend, Sendgrid, Mailgun and Postmark etc."
|
||||||
icon: rocket
|
icon: rocket
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ Quicklinks to set up your account and get started
|
|||||||
<Card
|
<Card
|
||||||
title="Add domain"
|
title="Add domain"
|
||||||
icon="globe"
|
icon="globe"
|
||||||
href="https://app.unsend.dev/domains"
|
href="https://app.usesend.com/domains"
|
||||||
>
|
>
|
||||||
Add domains to send emails
|
Add domains to send emails
|
||||||
</Card>
|
</Card>
|
||||||
@@ -20,7 +20,7 @@ Quicklinks to set up your account and get started
|
|||||||
<Card
|
<Card
|
||||||
title="Create API key"
|
title="Create API key"
|
||||||
icon="key"
|
icon="key"
|
||||||
href="https://app.unsend.dev/dev-settings/api-keys"
|
href="https://app.usesend.com/dev-settings/api-keys"
|
||||||
>
|
>
|
||||||
Generate API key to send emails from your app.
|
Generate API key to send emails from your app.
|
||||||
</Card>
|
</Card>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://mintlify.com/schema.json",
|
"$schema": "https://mintlify.com/schema.json",
|
||||||
"name": "Unsend",
|
"name": "useSend",
|
||||||
"logo": {
|
"logo": {
|
||||||
"dark": "/logo/Logo-wordmark-dark.png",
|
"dark": "/logo/Logo-wordmark-dark.png",
|
||||||
"light": "/logo/Logo-wordmark.png"
|
"light": "/logo/Logo-wordmark.png"
|
||||||
@@ -18,12 +18,12 @@
|
|||||||
"topbarLinks": [
|
"topbarLinks": [
|
||||||
{
|
{
|
||||||
"name": "Support",
|
"name": "Support",
|
||||||
"url": "mailto:hello@unsend.dev"
|
"url": "mailto:hey@usesend.com"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"topbarCtaButton": {
|
"topbarCtaButton": {
|
||||||
"name": "Dashboard",
|
"name": "Dashboard",
|
||||||
"url": "https://app.unsend.dev"
|
"url": "https://app.usesend.com"
|
||||||
},
|
},
|
||||||
"tabs": [
|
"tabs": [
|
||||||
{
|
{
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
{
|
{
|
||||||
"name": "GitHub",
|
"name": "GitHub",
|
||||||
"icon": "github",
|
"icon": "github",
|
||||||
"url": "https://github.com/unsend-dev/unsend"
|
"url": "https://github.com/usesend/usesend"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Community",
|
"name": "Community",
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
/** @type {import("eslint").Linter.Config} */
|
/** @type {import("eslint").Linter.Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
extends: ["@unsend/eslint-config/next.js"],
|
extends: ["@usesend/eslint-config/next.js"],
|
||||||
parser: "@typescript-eslint/parser",
|
parser: "@typescript-eslint/parser",
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: true,
|
project: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
36
apps/marketing/.gitignore
vendored
@@ -1,36 +0,0 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
.yarn/install-state.gz
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# next.js
|
|
||||||
/.next/
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
*.pem
|
|
||||||
|
|
||||||
# debug
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
# local env files
|
|
||||||
.env*.local
|
|
||||||
|
|
||||||
# vercel
|
|
||||||
.vercel
|
|
||||||
|
|
||||||
# typescript
|
|
||||||
*.tsbuildinfo
|
|
||||||
next-env.d.ts
|
|
@@ -1,36 +0,0 @@
|
|||||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
First, run the development server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
# or
|
|
||||||
yarn dev
|
|
||||||
# or
|
|
||||||
pnpm dev
|
|
||||||
# or
|
|
||||||
bun dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
||||||
|
|
||||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
|
||||||
|
|
||||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
To learn more about Next.js, take a look at the following resources:
|
|
||||||
|
|
||||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
||||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
||||||
|
|
||||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
|
||||||
|
|
||||||
## Deploy on Vercel
|
|
||||||
|
|
||||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
||||||
|
|
||||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
|
5
apps/marketing/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
12
apps/marketing/next.config.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/** @type {import("next").NextConfig} */
|
||||||
|
const config = {
|
||||||
|
// Static export for marketing site
|
||||||
|
output: "export",
|
||||||
|
images: {
|
||||||
|
// Required for static export if using images
|
||||||
|
unoptimized: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
|
@@ -1,6 +0,0 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
|
||||||
const nextConfig = {
|
|
||||||
output: "export",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default nextConfig;
|
|
@@ -2,33 +2,36 @@
|
|||||||
"name": "marketing",
|
"name": "marketing",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3001",
|
"dev": "next dev -p 3001",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "eslint . --max-warnings 0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/react": "^2.2.0",
|
"next": "^15.3.1",
|
||||||
"@unsend/email-editor": "workspace:*",
|
"react": "19.1.0",
|
||||||
"@unsend/ui": "workspace:*",
|
"react-dom": "19.1.0",
|
||||||
"date-fns": "^4.1.0",
|
"@usesend/ui": "workspace:*",
|
||||||
"framer-motion": "^12.9.2",
|
"@usesend/email-editor": "workspace:*"
|
||||||
"lucide-react": "^0.503.0",
|
|
||||||
"next": "15.3.1",
|
|
||||||
"react": "^19.1.0",
|
|
||||||
"react-dom": "^19.1.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@next/eslint-plugin-next": "^15.3.1",
|
||||||
|
"@types/eslint": "^9.6.1",
|
||||||
"@types/node": "^22.15.2",
|
"@types/node": "^22.15.2",
|
||||||
"@types/react": "^19.1.2",
|
"@types/react": "^19.1.2",
|
||||||
"@types/react-dom": "^19.1.2",
|
"@types/react-dom": "^19.1.2",
|
||||||
"@unsend/eslint-config": "workspace:*",
|
"@typescript-eslint/eslint-plugin": "^8.31.0",
|
||||||
"@unsend/tailwind-config": "workspace:*",
|
"@typescript-eslint/parser": "^8.31.0",
|
||||||
"autoprefixer": "^10.4.21",
|
"@usesend/eslint-config": "workspace:*",
|
||||||
"eslint": "^9.25.1",
|
"@usesend/tailwind-config": "workspace:*",
|
||||||
"eslint-config-next": "15.3.1",
|
"@usesend/typescript-config": "workspace:*",
|
||||||
|
"eslint": "^8.57.1",
|
||||||
|
"eslint-config-next": "^15.3.1",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
|
"prettier": "^3.5.3",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
}
|
}
|
||||||
|
8
apps/marketing/postcss.config.cjs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
|
|
@@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
};
|
|
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 89 KiB |
BIN
apps/marketing/public/logo-squircle.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
@@ -1,13 +0,0 @@
|
|||||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<mask id="mask0_11_17" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="512" height="512">
|
|
||||||
<rect width="512" height="512" fill="black"/>
|
|
||||||
</mask>
|
|
||||||
<g mask="url(#mask0_11_17)">
|
|
||||||
<path d="M375.776 63.6078C376.949 58.409 384.357 58.409 385.53 63.6078L435.035 282.926C436.338 288.703 428.294 291.55 425.673 286.239L385.137 204.087C383.303 200.37 378.003 200.37 376.169 204.087L335.632 286.239C333.012 291.55 324.967 288.703 326.271 282.926L375.776 63.6078Z" fill="#D9D9D9"/>
|
|
||||||
<path d="M400 312C400 338.264 394.827 364.272 384.776 388.537C374.725 412.802 359.993 434.85 341.421 453.421C322.85 471.993 300.802 486.725 276.537 496.776C252.272 506.827 226.264 512 200 512C173.736 512 147.728 506.827 123.463 496.776C99.1982 486.725 77.1504 471.993 58.5786 453.421C40.0069 434.85 25.275 412.802 15.2241 388.537C5.17315 364.272 -2.2961e-06 338.264 0 312L40.7078 312C40.7078 332.919 44.828 353.632 52.8332 372.958C60.8383 392.285 72.5717 409.845 87.3634 424.637C102.155 439.428 119.715 451.162 139.041 459.167C158.368 467.172 179.081 471.292 200 471.292C220.919 471.292 241.632 467.172 260.958 459.167C280.285 451.162 297.845 439.428 312.637 424.637C327.428 409.845 339.162 392.285 347.167 372.959C355.172 353.632 359.292 332.919 359.292 312H400Z" fill="#D9D9D9"/>
|
|
||||||
<path d="M0 20.5C0 9.17816 9.17816 0 20.5 0V0C31.8218 0 41 9.17816 41 20.5V310.5C41 321.822 31.8218 331 20.5 331V331C9.17816 331 0 321.822 0 310.5V20.5Z" fill="#D9D9D9"/>
|
|
||||||
<path d="M359 20.5C359 9.17816 368.178 0 379.5 0V0C390.822 0 400 9.17816 400 20.5V310.5C400 321.822 390.822 331 379.5 331V331C368.178 331 359 321.822 359 310.5V20.5Z" fill="#D9D9D9"/>
|
|
||||||
<path d="M362.06 10.2806C363.767 5.02805 369.408 2.15354 374.661 3.86019V3.86019C379.913 5.56684 382.788 11.2084 381.081 16.4609L284.977 312.239C283.27 317.492 277.629 320.367 272.376 318.66V318.66C267.123 316.953 264.249 311.312 265.956 306.059L362.06 10.2806Z" fill="#D9D9D9"/>
|
|
||||||
<path d="M378.96 17.9405C377.254 12.688 380.128 7.04642 385.381 5.33976V5.33976C390.633 3.63311 396.275 6.50762 397.981 11.7602L494.086 307.539C495.792 312.791 492.918 318.433 487.665 320.139V320.139C482.413 321.846 476.771 318.972 475.064 313.719L378.96 17.9405Z" fill="#D9D9D9"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 138 KiB |
@@ -1,120 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { Code } from "@unsend/ui/src/code";
|
|
||||||
|
|
||||||
const jsCode = `const requestOptions = {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Accept": "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Authorization": "Bearer us_1a2b3c4d5e6f7f8g"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
"to": "test@company.com",
|
|
||||||
"from": "hello@unsend.dev",
|
|
||||||
"subject": "Unsend email",
|
|
||||||
"html": "<p>Unsend is the best open source product to send emails</p>"
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
fetch("http://unsend.dev/api/v1/emails", requestOptions)
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(result => console.log(result))
|
|
||||||
.catch(error => console.error(error));
|
|
||||||
`;
|
|
||||||
|
|
||||||
const pythonCode = `import requests
|
|
||||||
import json
|
|
||||||
|
|
||||||
url = "http://unsend.dev/api/v1/emails"
|
|
||||||
|
|
||||||
payload = json.dumps({
|
|
||||||
"to": "test@company.com",
|
|
||||||
"from": "hello@unsend.dev",
|
|
||||||
"subject": "Unsend email",
|
|
||||||
"html": "<p>Unsend is the best open source product to send emails</p>"
|
|
||||||
})
|
|
||||||
headers = {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer us_1a2b3c4d5e6f7f8g'
|
|
||||||
}
|
|
||||||
|
|
||||||
response = requests.request("POST", url, headers=headers, data=payload)
|
|
||||||
|
|
||||||
print(response.text)`;
|
|
||||||
|
|
||||||
const rubyCode = `require 'uri'
|
|
||||||
require 'net/http'
|
|
||||||
require 'json'
|
|
||||||
|
|
||||||
url = URI("http://unsend.dev/api/v1/emails")
|
|
||||||
|
|
||||||
http = Net::HTTP.new(url.host, url.port)
|
|
||||||
request = Net::HTTP::Post.new(url)
|
|
||||||
request["Accept"] = 'application/json'
|
|
||||||
request["Content-Type"] = 'application/json'
|
|
||||||
request["Authorization"] = 'Bearer us_1a2b3c4d5e6f7f8g'
|
|
||||||
request.body = JSON.dump({
|
|
||||||
"to" => "test@company.com",
|
|
||||||
"from" => "hello@unsend.dev",
|
|
||||||
"subject" => "Unsend email",
|
|
||||||
"html" => "<p>Unsend is the best open source product to send emails</p>"
|
|
||||||
})
|
|
||||||
|
|
||||||
response = http.request(request)
|
|
||||||
puts response.read_body`;
|
|
||||||
|
|
||||||
const phpCode = `$url = "http://unsend.dev/api/v1/emails";
|
|
||||||
|
|
||||||
$payload = json_encode(array(
|
|
||||||
"to" => "test@company.com",
|
|
||||||
"from" => "hello@unsend.dev",
|
|
||||||
"subject" => "Unsend email",
|
|
||||||
"html" => "<p>Unsend is the best open source product to send emails</p>"
|
|
||||||
));
|
|
||||||
|
|
||||||
$headers = array(
|
|
||||||
"Accept: application/json",
|
|
||||||
"Content-Type: application/json",
|
|
||||||
"Authorization: Bearer us_1a2b3c4d5e6f7f8g"
|
|
||||||
);
|
|
||||||
|
|
||||||
$ch = curl_init($url);
|
|
||||||
curl_setopt($ch, CURLOPT_POST, true);
|
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
|
|
||||||
$response = curl_exec($ch);
|
|
||||||
if (curl_errno($ch)) {
|
|
||||||
echo 'Error:' . curl_error($ch);
|
|
||||||
} else {
|
|
||||||
echo $response;
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const cUrl = `curl --location 'https://unsend.dev/v1/emails' \\
|
|
||||||
--header 'Accept: application/json' \\
|
|
||||||
--header 'Content-Type: application/json' \\
|
|
||||||
--header 'Authorization: Bearer us_44c1071bd30058322f89a09805522d7341a47b5e' \\
|
|
||||||
--data-raw '{
|
|
||||||
"to": "test@company.com",
|
|
||||||
"from": "hello@unsend.dev",
|
|
||||||
"subject": "Unsend email",
|
|
||||||
"html": "<p>Unsend is the best open source product to send emails</p>",
|
|
||||||
}'`;
|
|
||||||
|
|
||||||
export default function IntegrationCode() {
|
|
||||||
return (
|
|
||||||
<Code
|
|
||||||
codeBlocks={[
|
|
||||||
{ language: "js", code: jsCode },
|
|
||||||
{ language: "ruby", code: rubyCode },
|
|
||||||
{ language: "php", code: phpCode },
|
|
||||||
{ language: "python", code: pythonCode },
|
|
||||||
{ language: "curl", code: cUrl },
|
|
||||||
]}
|
|
||||||
codeClassName="h-[500px] "
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -1,38 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { Editor } from "@unsend/email-editor";
|
|
||||||
import { Button } from "@unsend/ui/src/button";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
export default function EditorPage() {
|
|
||||||
const [json, setJson] = useState<Record<string, any>>({
|
|
||||||
type: "doc",
|
|
||||||
content: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const onConvertToHtml = async () => {
|
|
||||||
console.log(json)
|
|
||||||
const resp = await fetch("http://localhost:3000/api/to-html", {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(json),
|
|
||||||
});
|
|
||||||
|
|
||||||
const respJson = await resp.json();
|
|
||||||
console.log(respJson);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className=" max-w-2xl mx-auto">
|
|
||||||
<h1 className="text-center text-2xl py-8">
|
|
||||||
Try out unsend's email editor
|
|
||||||
</h1>
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<Button className="w-[200px]" onClick={onConvertToHtml}>
|
|
||||||
Convert to HTML
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Editor onUpdate={(editor) => setJson(editor.getJSON())} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -1,33 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--foreground-rgb: 0, 0, 0;
|
|
||||||
--background-start-rgb: 214, 219, 220;
|
|
||||||
--background-end-rgb: 255, 255, 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--foreground-rgb: 255, 255, 255;
|
|
||||||
--background-start-rgb: 0, 0, 0;
|
|
||||||
--background-end-rgb: 0, 0, 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
color: rgb(var(--foreground-rgb));
|
|
||||||
background: linear-gradient(
|
|
||||||
to bottom,
|
|
||||||
transparent,
|
|
||||||
rgb(var(--background-end-rgb))
|
|
||||||
)
|
|
||||||
rgb(var(--background-start-rgb));
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer utilities {
|
|
||||||
.text-balance {
|
|
||||||
text-wrap: balance;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,149 +1,38 @@
|
|||||||
import "@unsend/ui/styles/globals.css";
|
import "@usesend/ui/styles/globals.css";
|
||||||
import type { Metadata } from "next";
|
|
||||||
import { Inter } from "next/font/google";
|
|
||||||
import { ThemeProvider } from "@unsend/ui";
|
|
||||||
import Script from "next/script";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { TextWithCopyButton } from "@unsend/ui/src/text-with-copy";
|
|
||||||
import Image from "next/image";
|
|
||||||
import { DocumentChartBarIcon } from "@heroicons/react/24/solid";
|
|
||||||
import { Book } from "lucide-react";
|
|
||||||
import { Separator } from "@unsend/ui/src/separator";
|
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
import { Inter } from "next/font/google";
|
||||||
|
import { JetBrains_Mono } from "next/font/google";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { ThemeProvider } from "@usesend/ui";
|
||||||
|
|
||||||
|
const inter = Inter({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-sans",
|
||||||
|
});
|
||||||
|
|
||||||
|
const jetbrainsMono = JetBrains_Mono({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-mono",
|
||||||
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Unsend",
|
title: "useSend - Open source email platform",
|
||||||
description: "Open source sending infrastructure for developers",
|
description: "Open source email platform for everyone",
|
||||||
icons: [{ rel: "icon", url: "/favicon.ico" }],
|
icons: [{ rel: "icon", url: "/favicon.ico" }],
|
||||||
twitter: {
|
|
||||||
card: "summary_large_image",
|
|
||||||
site: "https://unsend.dev",
|
|
||||||
title: "Unsend",
|
|
||||||
description: "Open source sending infrastructure for developers",
|
|
||||||
images: "https://unsend.dev/og_banner.png",
|
|
||||||
creator: "@KM_Koushik_",
|
|
||||||
},
|
|
||||||
openGraph: {
|
|
||||||
type: "website",
|
|
||||||
title: "Unsend",
|
|
||||||
description: "Open source sending infrastructure for developers",
|
|
||||||
siteName: "Unsend",
|
|
||||||
url: "https://unsend.dev",
|
|
||||||
images: "https://unsend.dev/og_banner.png",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en" suppressHydrationWarning className="bg-sidebar-background">
|
||||||
{process.env.NODE_ENV === "production" && (
|
<body
|
||||||
<Script src="https://scripts.simpleanalyticscdn.com/latest.js" />
|
className={`font-sans ${inter.variable} ${jetbrainsMono.variable} bg-sidebar-background`}
|
||||||
)}
|
>
|
||||||
<body className={inter.className}>
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||||
<ThemeProvider attribute="class" defaultTheme="dark">
|
{children}
|
||||||
<div className="bg-[#0c0e12] pb-20 h-full">
|
|
||||||
<div className=" mx-auto w-full lg:max-w-6xl relative flex flex-col ">
|
|
||||||
<nav className="p-4 flex justify-between">
|
|
||||||
<div className="text-2xl font-semibold">
|
|
||||||
<Link href="/">Unsend</Link>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-8 items-center">
|
|
||||||
<Link href="https://docs.unsend.dev" target="_blank" className="flex items-center gap-1 bg-border/70 text-white px-3 py-1 rounded-full">
|
|
||||||
<Book className="h-6 w-6 fill-white stroke-border" /> Docs
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="https://github.com/unsend-dev/unsend"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 496 512"
|
|
||||||
className="h-6 w-6 stroke-white fill-white"
|
|
||||||
>
|
|
||||||
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
|
|
||||||
</svg>
|
|
||||||
</Link>
|
|
||||||
<Link href="https://twitter.com/unsend_dev" target="_blank">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
className="h-6 w-6 stroke-white fill-white"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<path d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z" />
|
|
||||||
</svg>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="https://discord.gg/BU8n8pJv8S"
|
|
||||||
target="_blank"
|
|
||||||
className="flex gap-2 items-center"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 640 512"
|
|
||||||
className="h-6 w-6 stroke-white fill-white"
|
|
||||||
>
|
|
||||||
<path d="M524.5 69.8a1.5 1.5 0 0 0 -.8-.7A485.1 485.1 0 0 0 404.1 32a1.8 1.8 0 0 0 -1.9 .9 337.5 337.5 0 0 0 -14.9 30.6 447.8 447.8 0 0 0 -134.4 0 309.5 309.5 0 0 0 -15.1-30.6 1.9 1.9 0 0 0 -1.9-.9A483.7 483.7 0 0 0 116.1 69.1a1.7 1.7 0 0 0 -.8 .7C39.1 183.7 18.2 294.7 28.4 404.4a2 2 0 0 0 .8 1.4A487.7 487.7 0 0 0 176 479.9a1.9 1.9 0 0 0 2.1-.7A348.2 348.2 0 0 0 208.1 430.4a1.9 1.9 0 0 0 -1-2.6 321.2 321.2 0 0 1 -45.9-21.9 1.9 1.9 0 0 1 -.2-3.1c3.1-2.3 6.2-4.7 9.1-7.1a1.8 1.8 0 0 1 1.9-.3c96.2 43.9 200.4 43.9 295.5 0a1.8 1.8 0 0 1 1.9 .2c2.9 2.4 6 4.9 9.1 7.2a1.9 1.9 0 0 1 -.2 3.1 301.4 301.4 0 0 1 -45.9 21.8 1.9 1.9 0 0 0 -1 2.6 391.1 391.1 0 0 0 30 48.8 1.9 1.9 0 0 0 2.1 .7A486 486 0 0 0 610.7 405.7a1.9 1.9 0 0 0 .8-1.4C623.7 277.6 590.9 167.5 524.5 69.8zM222.5 337.6c-29 0-52.8-26.6-52.8-59.2S193.1 219.1 222.5 219.1c29.7 0 53.3 26.8 52.8 59.2C275.3 311 251.9 337.6 222.5 337.6zm195.4 0c-29 0-52.8-26.6-52.8-59.2S388.4 219.1 417.9 219.1c29.7 0 53.3 26.8 52.8 59.2C470.7 311 447.5 337.6 417.9 337.6z" />
|
|
||||||
</svg>
|
|
||||||
</Link>
|
|
||||||
{/* <Link href="https://github.com/unsendhq">GitHub</Link> */}
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
<div className="max-w-6xl mx-auto px-4">{children}</div>
|
|
||||||
<div className="flex justify-between mt-20 max-w-6xl mx-auto px-4 md:px-0 pb-10">
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<TextWithCopyButton value="hello@unsend.dev" />
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-8 items-center">
|
|
||||||
<Link href="https://docs.unsend.dev">docs</Link>
|
|
||||||
<Separator orientation="vertical" />
|
|
||||||
<Link href="/terms">terms</Link>
|
|
||||||
<Link href="/privacy">privacy</Link>
|
|
||||||
<Link
|
|
||||||
href="https://github.com/unsend-dev/unsend"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 496 512"
|
|
||||||
className="h-6 w-6 stroke-white fill-white"
|
|
||||||
>
|
|
||||||
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
|
|
||||||
</svg>
|
|
||||||
</Link>
|
|
||||||
<Link href="https://twitter.com/unsend_dev" target="_blank">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
className="h-6 w-6 stroke-white fill-white"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<path d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z" />
|
|
||||||
</svg>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="https://discord.gg/BU8n8pJv8S"
|
|
||||||
target="_blank"
|
|
||||||
className="flex gap-2 items-center"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 640 512"
|
|
||||||
className="h-6 w-6 stroke-white fill-white"
|
|
||||||
>
|
|
||||||
<path d="M524.5 69.8a1.5 1.5 0 0 0 -.8-.7A485.1 485.1 0 0 0 404.1 32a1.8 1.8 0 0 0 -1.9 .9 337.5 337.5 0 0 0 -14.9 30.6 447.8 447.8 0 0 0 -134.4 0 309.5 309.5 0 0 0 -15.1-30.6 1.9 1.9 0 0 0 -1.9-.9A483.7 483.7 0 0 0 116.1 69.1a1.7 1.7 0 0 0 -.8 .7C39.1 183.7 18.2 294.7 28.4 404.4a2 2 0 0 0 .8 1.4A487.7 487.7 0 0 0 176 479.9a1.9 1.9 0 0 0 2.1-.7A348.2 348.2 0 0 0 208.1 430.4a1.9 1.9 0 0 0 -1-2.6 321.2 321.2 0 0 1 -45.9-21.9 1.9 1.9 0 0 1 -.2-3.1c3.1-2.3 6.2-4.7 9.1-7.1a1.8 1.8 0 0 1 1.9-.3c96.2 43.9 200.4 43.9 295.5 0a1.8 1.8 0 0 1 1.9 .2c2.9 2.4 6 4.9 9.1 7.2a1.9 1.9 0 0 1 -.2 3.1 301.4 301.4 0 0 1 -45.9 21.8 1.9 1.9 0 0 0 -1 2.6 391.1 391.1 0 0 0 30 48.8 1.9 1.9 0 0 0 2.1 .7A486 486 0 0 0 610.7 405.7a1.9 1.9 0 0 0 .8-1.4C623.7 277.6 590.9 167.5 524.5 69.8zM222.5 337.6c-29 0-52.8-26.6-52.8-59.2S193.1 219.1 222.5 219.1c29.7 0 53.3 26.8 52.8 59.2C275.3 311 251.9 337.6 222.5 337.6zm195.4 0c-29 0-52.8-26.6-52.8-59.2S388.4 219.1 417.9 219.1c29.7 0 53.3 26.8 52.8 59.2C470.7 311 447.5 337.6 417.9 337.6z" />
|
|
||||||
</svg>
|
|
||||||
</Link>
|
|
||||||
{/* <Link href="https://github.com/unsendhq">GitHub</Link> */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -1,189 +1,27 @@
|
|||||||
import { EnvelopeIcon, MegaphoneIcon } from "@heroicons/react/24/solid";
|
import Image from "next/image";
|
||||||
import {
|
import { GitHubStarsButton } from "~/components/GitHubStarsButton";
|
||||||
Heading1,
|
|
||||||
Heading2,
|
|
||||||
Heading3,
|
|
||||||
AlignLeft,
|
|
||||||
AlignRight,
|
|
||||||
AlignCenter,
|
|
||||||
Bold,
|
|
||||||
Italic,
|
|
||||||
ListOrdered,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { formatDate } from "date-fns";
|
|
||||||
|
|
||||||
import IntegrationCode from "./IntegrationCode";
|
export default function Page() {
|
||||||
import {
|
|
||||||
GitHubStarButton,
|
|
||||||
HeroImage,
|
|
||||||
JoinWaitlist,
|
|
||||||
} from "~/components/landind-page";
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
return (
|
||||||
<div className="pb-20">
|
<main className="min-h-screen flex items-center justify-center p-6">
|
||||||
<div className=" mx-auto w-full lg:max-w-6xl relative flex flex-col ">
|
<div className="max-w-2xl text-center space-y-4">
|
||||||
<div className="p-4 mt-20">
|
<div className="flex justify-center">
|
||||||
<h1 className="relative z-10 text-neutral-100 text-2xl lg:max-w-4xl mx-auto md:text-6xl md:leading-[4.5rem] text-center font-sans font-bold">
|
<Image
|
||||||
Open source sending infrastructure for{" "}
|
src="/logo-squircle.png"
|
||||||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-[#06b6d4] to-[#10b981]">
|
alt="useSend logo"
|
||||||
developers
|
width={64}
|
||||||
</span>
|
height={64}
|
||||||
</h1>
|
priority
|
||||||
<p></p>
|
/>
|
||||||
<p className="text-neutral-100 text-lg max-w-lg mx-auto my-4 text-center relative z-10">
|
|
||||||
Send transactional, marketing emails, SMSes and push notifications
|
|
||||||
effortlessly.
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-col items-center lg:flex-row justify-center mt-16 gap-8">
|
|
||||||
<JoinWaitlist />
|
|
||||||
<GitHubStarButton />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<HeroImage />
|
|
||||||
</div>
|
</div>
|
||||||
|
<h1 className="text-xl font-mono font-medium text-blue">useSend</h1>
|
||||||
{/* <BackgroundBeams /> */}
|
<p className="text-muted-foreground">
|
||||||
</div>
|
Open source email platform for everyone
|
||||||
<div className="w-full lg:max-w-6xl mx-auto flex flex-col gap-40 mt-40 md:px-6 px-0">
|
</p>
|
||||||
<div className="space-y-12">
|
<div className="mt-6 flex items-center justify-center gap-3">
|
||||||
<div>
|
<GitHubStarsButton />
|
||||||
<p className="text-center font-semibold text-3xl lg:text-6xl">
|
|
||||||
Reach your users</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-10 flex-col lg:flex-row md:p-8 p-3 bg-[#0c0e12] rounded-lg">
|
|
||||||
<div className="lg:w-1/2">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<EnvelopeIcon className="h-10 w-10 text-fuchsia-500" />
|
|
||||||
<p className="text-3xl font-semibold">Transactional Mail</p>
|
|
||||||
</div>
|
|
||||||
<ul className="flex flex-col gap-4 mt-8">
|
|
||||||
<li>Simple to use! No wasted time on configuration.</li>
|
|
||||||
<li>Send emails that reach the inbox, not spam.</li>
|
|
||||||
<li>Get notified of email bounces and complaints.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div className="lg:w-1/2 flex flex-col bg-[#0e1217] border rounded-lg p-8">
|
|
||||||
<div className=" border-l border-dashed flex flex-col gap-8">
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<div className="flex gap-5 items-start">
|
|
||||||
<div className=" -ml-2.5">
|
|
||||||
<div
|
|
||||||
className={`flex justify-center items-center p-1.5 bg-gray-600/50 rounded-full`}
|
|
||||||
>
|
|
||||||
<div className={`h-2 w-2 rounded-full bg-gray-600`}></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="-mt-1 ">
|
|
||||||
<div className=" capitalize font-medium">
|
|
||||||
<div
|
|
||||||
className={` text-center w-[130px] rounded capitalize py-1 text-xs bg-gray-400/10 text-gray-400 border-gray-400/10`}
|
|
||||||
>
|
|
||||||
Sent
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="text-xs text-muted-foreground mt-2"
|
|
||||||
suppressHydrationWarning
|
|
||||||
>
|
|
||||||
{formatDate(Date.now() - 100000, "MMM dd, hh:mm a")}
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 text-primary/80">
|
|
||||||
We received your request and sent the email to recipient's
|
|
||||||
server.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="flex gap-5 items-start">
|
|
||||||
<div className="-ml-2.5">
|
|
||||||
<div
|
|
||||||
className={`flex justify-center items-center p-1.5 bg-emerald-500/50 rounded-full`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`h-2 w-2 rounded-full bg-emerald-500`}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="-mt-1">
|
|
||||||
<div className=" capitalize font-medium">
|
|
||||||
<div
|
|
||||||
className={` text-center w-[130px] rounded capitalize py-1 text-xs bg-emerald-500/10 text-emerald-500 border-emerald-600/10`}
|
|
||||||
>
|
|
||||||
Delivered
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="text-xs text-muted-foreground mt-2"
|
|
||||||
suppressHydrationWarning
|
|
||||||
>
|
|
||||||
{formatDate(new Date(), "MMM dd, hh:mm a")}
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 text-primary/80">
|
|
||||||
Mail is successfully delivered to the recipient.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-10 flex-col lg:flex-row md:p-8 p-3 bg-[#0c0e12] rounded-lg">
|
|
||||||
<div className="lg:w-1/2">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<MegaphoneIcon className="h-10 w-10 text-indigo-500" />
|
|
||||||
<p className="text-3xl font-semibold">Marketing Mail</p>
|
|
||||||
</div>
|
|
||||||
<ul className="flex flex-col gap-4 mt-8">
|
|
||||||
<li>Manage newsletters, changelogs, and broadcasts easily.</li>
|
|
||||||
<li>
|
|
||||||
Use our no-code email builder and templates that works on all
|
|
||||||
email clients.
|
|
||||||
</li>
|
|
||||||
<li>Measure engagement using click and open tracking.</li>
|
|
||||||
<li>
|
|
||||||
Focus on the content and we will handle the subscription for
|
|
||||||
you.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div className="lg:w-1/2">
|
|
||||||
<div className="w-full rounded-lg border bg-[#0e1217]">
|
|
||||||
<div className="flex gap-4 justify-between border-b p-4 overflow-auto">
|
|
||||||
<Heading1 />
|
|
||||||
<Heading2 />
|
|
||||||
<Heading3 />
|
|
||||||
<AlignLeft />
|
|
||||||
<AlignCenter />
|
|
||||||
<AlignRight />
|
|
||||||
<Bold />
|
|
||||||
<Italic />
|
|
||||||
<ListOrdered />
|
|
||||||
</div>
|
|
||||||
<div className="h-[200px] p-4">
|
|
||||||
<div className="">
|
|
||||||
<div className="text-xl text-center">Welcome to unsend!</div>
|
|
||||||
<p className="text-center mt-8">
|
|
||||||
Finally an open source alternative for Resend, Mailgun,
|
|
||||||
Sendgrid and postmark.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-20 rounded-lg md:p-8 p-3">
|
|
||||||
<p className="text-center font-semibold text-3xl lg:text-6xl ">
|
|
||||||
Integrate in minutes
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="mt-10">
|
|
||||||
<IntegrationCode />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,154 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
const PrivacyPolicy = () => {
|
|
||||||
return (
|
|
||||||
<div className="mx-auto mt-20">
|
|
||||||
<div className="flex flex-col gap-12">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-semibold">Privacy Policy</h1>
|
|
||||||
<p className="mb-4 text-muted-foreground">
|
|
||||||
Last Updated: Aug 22, 2024
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p className="mb-4 text-primary">
|
|
||||||
Unsend is committed to protecting your privacy. This Privacy Policy
|
|
||||||
outlines how we collect, use, and disclose your information when you
|
|
||||||
use Unsend.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-xl font-semibold mb-4">
|
|
||||||
1. Information We Collect
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<h3 className="text-lg font-semibold mt-4 mb-2">
|
|
||||||
Personal Information
|
|
||||||
</h3>
|
|
||||||
<p className="mb-4 opacity-90 ">
|
|
||||||
When you create an account, we collect your email address and name.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3 className="text-lg font-semibold mt-4 mb-2">Usage Data</h3>
|
|
||||||
<p className="mb-4 opacity-90 ">
|
|
||||||
We automatically collect information about how you interact with the
|
|
||||||
Service, such as pages visited and features used.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold mb-4">
|
|
||||||
2. How We Use Your Information
|
|
||||||
</h2>
|
|
||||||
<p className="mb-4 opacity-90 ">
|
|
||||||
We use your information for the following purposes:
|
|
||||||
</p>
|
|
||||||
<ul className="list-disc list-inside mb-4 opacity-90">
|
|
||||||
<li>To provide and maintain the Service</li>
|
|
||||||
<li>To improve and personalize your experience with the Service</li>
|
|
||||||
<li>
|
|
||||||
To communicate with you about updates, promotions, and customer
|
|
||||||
support
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold mb-4">
|
|
||||||
3. Sharing Your Information
|
|
||||||
</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
We do not sell, rent or your personal information with third
|
|
||||||
parties.
|
|
||||||
</p>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
We are using following third party services to run this service.
|
|
||||||
</p>
|
|
||||||
<ul className="list-disc list-inside mb-4 opacity-90 gap-2 flex flex-col mt-2">
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://railway.app/"
|
|
||||||
className=" underline"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Railway
|
|
||||||
</a>
|
|
||||||
: this is where unsend is hosted. currently in US region
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://aws.amazon.com/ses/"
|
|
||||||
className=" underline"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
AWS
|
|
||||||
</a>
|
|
||||||
: unsend uses AWS SES to process your mails
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold mb-4">4. Data Security</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
We take reasonable steps to protect your information from
|
|
||||||
unauthorized access, use, or disclosure. However, no method of
|
|
||||||
transmission or storage is completely secure, and we cannot
|
|
||||||
guarantee the absolute security of your information.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold mb-4">5. Data Retention</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
We retain your personal information for as long as necessary to
|
|
||||||
provide the Service, comply with legal obligations, resolve
|
|
||||||
disputes, and enforce our agreements.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold mb-4">6. Your Rights</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
You may access, update, or request the deletion of your personal
|
|
||||||
information by contacting us at hello@unsend.dev.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold mb-4">7. Children's Privacy</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
The Service is not intended for users under 13 years old. We do not
|
|
||||||
knowingly collect personal information from children under 13. If
|
|
||||||
you are a parent or guardian and believe your child has provided us
|
|
||||||
with personal information, please contact us at hello@unsend.dev.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold mb-4">
|
|
||||||
8. Changes to This Policy
|
|
||||||
</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
We may update this Policy from time to time. We will notify you of
|
|
||||||
any changes by posting the updated Policy on this page. By
|
|
||||||
continuing to use the Service, you agree to be bound by the updated
|
|
||||||
Policy.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold mb-4">9. Contact</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
If you have any questions or concerns regarding this Privacy Policy,
|
|
||||||
please contact us at hello@unsend.dev.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PrivacyPolicy;
|
|
@@ -1,97 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
const TermsOfService = () => {
|
|
||||||
return (
|
|
||||||
<div className="mx-auto mt-20">
|
|
||||||
<div className="flex flex-col gap-12">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-semibold">Terms of Service</h1>
|
|
||||||
<p className="mb-4 text-muted-foreground">
|
|
||||||
Last Updated: Apr 22, 2024
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p className="mb-4 text-primary">
|
|
||||||
By using Unsend, you agree to these Terms of Service. Unsend
|
|
||||||
reserves the right to modify these Terms at any time. By continuing
|
|
||||||
to use the Service, you agree to the updated Terms.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-xl font-semibold mb-4">1. Agreement</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
By using Unsend, you agree to these Terms of Service. Unsend
|
|
||||||
reserves the right to modify these Terms at any time. By continuing
|
|
||||||
to use the Service, you agree to the updated Terms.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-xl font-semibold mb-4">2. Eligibility</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
You must be at least 13 years old to use the Service. By using the
|
|
||||||
Service, you represent that you meet this age requirement.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-xl font-semibold mb-4">3. Acceptable Use</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
You agree not to use the Service for any illegal or harmful
|
|
||||||
activities. We reserve the right to terminate your access to the
|
|
||||||
Service if you violate this provision.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-xl font-semibold mb-4">4. Termination</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
We reserve the right to suspend or terminate your access to the
|
|
||||||
Service at any time, with or without notice, for any reason.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-xl font-semibold mb-4">
|
|
||||||
5. Disclaimers and Limitation of Liability
|
|
||||||
</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
The Service is provided "as is" and "as available," without
|
|
||||||
warranties of any kind. We disclaim all liability for any damages or
|
|
||||||
losses arising from your use of the Service.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-xl font-semibold mb-4">6. Governing Law</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
These Terms shall be governed by the laws of US. Any disputes
|
|
||||||
arising from these Terms shall be resolved in the courts located in
|
|
||||||
US.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-xl font-semibold mb-4">7. Privacy</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
Please read our{" "}
|
|
||||||
<a className="underline" href="/privacy">
|
|
||||||
privacy policy
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-xl font-semibold mb-4">8. Contact</h2>
|
|
||||||
<p className="mb-4 opacity-90">
|
|
||||||
If you have any questions or concerns regarding these Terms, please
|
|
||||||
contact us at hello@unsend.dev.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TermsOfService;
|
|
73
apps/marketing/src/components/GitHubStarsButton.tsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Button } from "@usesend/ui/src/button";
|
||||||
|
|
||||||
|
const REPO = "unsend-dev/unsend";
|
||||||
|
const REPO_URL = `https://github.com/${REPO}`;
|
||||||
|
const API_URL = `https://api.github.com/repos/${REPO}`;
|
||||||
|
|
||||||
|
export function GitHubStarsButton() {
|
||||||
|
const [stars, setStars] = useState<number | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
async function load() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(API_URL, {
|
||||||
|
headers: {
|
||||||
|
Accept: "application/vnd.github+json",
|
||||||
|
"X-GitHub-Api-Version": "2022-11-28",
|
||||||
|
},
|
||||||
|
cache: "no-store",
|
||||||
|
});
|
||||||
|
if (!res.ok) return;
|
||||||
|
const json = await res.json();
|
||||||
|
if (!cancelled && typeof json.stargazers_count === "number") {
|
||||||
|
setStars(json.stargazers_count);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// ignore network errors; keep placeholder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const formatted = stars?.toLocaleString() ?? "—";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button variant="outline" size="lg" className="px-4 gap-2">
|
||||||
|
<a
|
||||||
|
href={REPO_URL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="Star this repo on GitHub"
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<GitHubIcon className="h-4 w-4" />
|
||||||
|
<span>Star on GitHub</span>
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GitHubIcon({ className = "" }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
className={className}
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M12 2C6.477 2 2 6.486 2 12.021c0 4.424 2.865 8.175 6.839 9.5.5.093.682-.217.682-.483 0-.237-.009-.864-.014-1.696-2.782.605-3.369-1.343-3.369-1.343-.454-1.155-1.11-1.463-1.11-1.463-.907-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.833.091-.647.35-1.088.636-1.338-2.221-.253-4.555-1.113-4.555-4.952 0-1.093.39-1.987 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.027A9.564 9.564 0 0 1 12 6.844a9.56 9.56 0 0 1 2.504.337c1.909-1.297 2.748-1.027 2.748-1.027.545 1.378.202 2.397.1 2.65.64.701 1.028 1.595 1.028 2.688 0 3.848-2.337 4.697-4.565 4.945.36.31.68.921.68 1.856 0 1.339-.012 2.418-.012 2.747 0 .268.18.581.688.482C19.138 20.193 22 16.443 22 12.02 22 6.486 17.523 2 12 2z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
@@ -1,68 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import { DocumentIcon } from "@heroicons/react/24/solid";
|
|
||||||
import { RocketLaunchIcon } from "@heroicons/react/24/solid";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
|
|
||||||
export function JoinWaitlist() {
|
|
||||||
return (
|
|
||||||
<motion.a
|
|
||||||
className="bg-white text-black py-2 px-6 rounded-full cursor-pointer flex justify-center gap-2 w-[220px]"
|
|
||||||
whileHover={{ scale: 1.05 }}
|
|
||||||
transition={{ type: "spring", stiffness: 400, damping: 10 }}
|
|
||||||
href="https://app.youform.io/forms/caja89vr"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<RocketLaunchIcon className="h-6 w-6" />
|
|
||||||
Join the waitlist
|
|
||||||
</motion.a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GitHubStarButton() {
|
|
||||||
return (
|
|
||||||
<motion.a
|
|
||||||
className="border-white border py-2 px-6 rounded-full justify-center cursor-pointer flex gap-2 w-[220px]"
|
|
||||||
whileHover={{ scale: 1.05 }}
|
|
||||||
transition={{ type: "spring", stiffness: 400, damping: 10 }}
|
|
||||||
href="https://github.com/unsend-dev/unsend"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 496 512"
|
|
||||||
className="h-6 w-6 stroke-white fill-white"
|
|
||||||
>
|
|
||||||
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
|
|
||||||
</svg>
|
|
||||||
Star us on github
|
|
||||||
</motion.a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function HeroImage() {
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, scale: 0.8 }}
|
|
||||||
whileInView={{ opacity: 1, scale: 1 }}
|
|
||||||
transition={{
|
|
||||||
duration: 0.3,
|
|
||||||
damping: 15,
|
|
||||||
stiffness: 100,
|
|
||||||
type: "spring",
|
|
||||||
}}
|
|
||||||
viewport={{ once: true }}
|
|
||||||
className="p-3 bg-[#0c0e12] mt-24 rounded-xl mx-2 shadow-[#1e293b]/70 shadow-md border-2 border-border/70"
|
|
||||||
>
|
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
|
||||||
src="/app.webp"
|
|
||||||
alt="App"
|
|
||||||
width={1200}
|
|
||||||
height={800}
|
|
||||||
className="rounded-lg relative"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -1,149 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import React from "react";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import { cn } from "@unsend/ui/lib/utils";
|
|
||||||
|
|
||||||
export const BackgroundBeams = React.memo(
|
|
||||||
({ className }: { className?: string }) => {
|
|
||||||
const paths = [
|
|
||||||
"M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875",
|
|
||||||
"M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867",
|
|
||||||
"M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859",
|
|
||||||
"M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851",
|
|
||||||
"M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843",
|
|
||||||
"M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835",
|
|
||||||
"M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827",
|
|
||||||
"M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819",
|
|
||||||
"M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811",
|
|
||||||
"M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803",
|
|
||||||
"M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795",
|
|
||||||
"M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787",
|
|
||||||
"M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779",
|
|
||||||
"M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771",
|
|
||||||
"M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763",
|
|
||||||
"M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755",
|
|
||||||
"M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747",
|
|
||||||
"M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739",
|
|
||||||
"M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731",
|
|
||||||
"M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723",
|
|
||||||
"M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715",
|
|
||||||
"M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707",
|
|
||||||
"M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699",
|
|
||||||
"M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691",
|
|
||||||
"M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683",
|
|
||||||
"M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675",
|
|
||||||
"M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667",
|
|
||||||
"M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659",
|
|
||||||
"M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651",
|
|
||||||
"M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643",
|
|
||||||
"M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635",
|
|
||||||
"M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627",
|
|
||||||
"M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619",
|
|
||||||
"M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611",
|
|
||||||
"M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603",
|
|
||||||
"M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595",
|
|
||||||
"M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587",
|
|
||||||
"M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579",
|
|
||||||
"M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571",
|
|
||||||
"M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563",
|
|
||||||
"M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555",
|
|
||||||
"M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547",
|
|
||||||
"M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539",
|
|
||||||
"M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531",
|
|
||||||
"M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523",
|
|
||||||
"M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515",
|
|
||||||
"M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507",
|
|
||||||
"M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499",
|
|
||||||
"M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491",
|
|
||||||
"M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483",
|
|
||||||
];
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"absolute h-full w-full inset-0 [mask-size:40px] [mask-repeat:no-repeat] flex items-center justify-center",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
className=" z-0 h-full w-full pointer-events-none absolute "
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
viewBox="0 0 696 316"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483M-30 -589C-30 -589 38 -184 502 -57C966 70 1034 475 1034 475M-23 -597C-23 -597 45 -192 509 -65C973 62 1041 467 1041 467M-16 -605C-16 -605 52 -200 516 -73C980 54 1048 459 1048 459M-9 -613C-9 -613 59 -208 523 -81C987 46 1055 451 1055 451M-2 -621C-2 -621 66 -216 530 -89C994 38 1062 443 1062 443M5 -629C5 -629 73 -224 537 -97C1001 30 1069 435 1069 435M12 -637C12 -637 80 -232 544 -105C1008 22 1076 427 1076 427M19 -645C19 -645 87 -240 551 -113C1015 14 1083 419 1083 419"
|
|
||||||
stroke="url(#paint0_radial_242_278)"
|
|
||||||
strokeOpacity="0.05"
|
|
||||||
strokeWidth="0.5"
|
|
||||||
></path>
|
|
||||||
|
|
||||||
{paths.map((path, index) => (
|
|
||||||
<motion.path
|
|
||||||
key={`path-` + index}
|
|
||||||
d={path}
|
|
||||||
stroke={`url(#linearGradient-${index})`}
|
|
||||||
strokeOpacity="0.4"
|
|
||||||
strokeWidth="0.5"
|
|
||||||
></motion.path>
|
|
||||||
))}
|
|
||||||
<defs>
|
|
||||||
{paths.map((path, index) => (
|
|
||||||
<motion.linearGradient
|
|
||||||
id={`linearGradient-${index}`}
|
|
||||||
key={`gradient-${index}`}
|
|
||||||
initial={{
|
|
||||||
x1: "0%",
|
|
||||||
x2: "0%",
|
|
||||||
y1: "0%",
|
|
||||||
y2: "0%",
|
|
||||||
}}
|
|
||||||
animate={{
|
|
||||||
x1: ["0%", "100%"],
|
|
||||||
x2: ["0%", "95%"],
|
|
||||||
y1: ["0%", "100%"],
|
|
||||||
y2: ["0%", `${93 + Math.random() * 8}%`],
|
|
||||||
}}
|
|
||||||
transition={{
|
|
||||||
duration: Math.random() * 10 + 10,
|
|
||||||
ease: "easeInOut",
|
|
||||||
repeat: Infinity,
|
|
||||||
delay: Math.random() * 10,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<stop stopColor="#18CCFC" stopOpacity="0"></stop>
|
|
||||||
<stop stopColor="#18CCFC"></stop>
|
|
||||||
<stop offset="32.5%" stopColor="#06b6d4"></stop>
|
|
||||||
<stop offset="100%" stopColor="#10b981" stopOpacity="0"></stop>
|
|
||||||
</motion.linearGradient>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<radialGradient
|
|
||||||
id="paint0_radial_242_278"
|
|
||||||
cx="0"
|
|
||||||
cy="0"
|
|
||||||
r="1"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
gradientTransform="translate(352 34) rotate(90) scale(555 1560.62)"
|
|
||||||
>
|
|
||||||
<stop
|
|
||||||
offset="0.066667"
|
|
||||||
stopColor="#18CCFC"
|
|
||||||
stopOpacity="1"
|
|
||||||
></stop>
|
|
||||||
<stop
|
|
||||||
offset="0.243243"
|
|
||||||
stopColor="#18CCFC"
|
|
||||||
stopOpacity="0.5"
|
|
||||||
></stop>
|
|
||||||
<stop offset="0.43594" stopColor="white" stopOpacity="0.1"></stop>
|
|
||||||
</radialGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
BackgroundBeams.displayName = "BackgroundBeams";
|
|
@@ -1,61 +0,0 @@
|
|||||||
// Input component extends from shadcnui - https://ui.shadcn.com/docs/components/input
|
|
||||||
"use client";
|
|
||||||
import * as React from "react";
|
|
||||||
import { cn } from "@unsend/ui/lib/utils";
|
|
||||||
import { useMotionTemplate, useMotionValue, motion } from "framer-motion";
|
|
||||||
|
|
||||||
export interface InputProps
|
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
|
||||||
|
|
||||||
const StyledInput = React.forwardRef<HTMLInputElement, InputProps>(
|
|
||||||
({ className, type, ...props }, ref) => {
|
|
||||||
const radius = 100; // change this to increase the rdaius of the hover effect
|
|
||||||
const [visible, setVisible] = React.useState(false);
|
|
||||||
|
|
||||||
let mouseX = useMotionValue(0);
|
|
||||||
let mouseY = useMotionValue(0);
|
|
||||||
|
|
||||||
function handleMouseMove({ currentTarget, clientX, clientY }: any) {
|
|
||||||
let { left, top } = currentTarget.getBoundingClientRect();
|
|
||||||
|
|
||||||
mouseX.set(clientX - left);
|
|
||||||
mouseY.set(clientY - top);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
style={{
|
|
||||||
background: useMotionTemplate`
|
|
||||||
radial-gradient(
|
|
||||||
${visible ? radius + "px" : "0px"} circle at ${mouseX}px ${mouseY}px,
|
|
||||||
#06b6d4,
|
|
||||||
transparent 80%
|
|
||||||
)
|
|
||||||
`,
|
|
||||||
}}
|
|
||||||
onMouseMove={handleMouseMove}
|
|
||||||
onMouseEnter={() => setVisible(true)}
|
|
||||||
onMouseLeave={() => setVisible(false)}
|
|
||||||
className="p-[2px] rounded-lg transition duration-300 group/input"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type={type}
|
|
||||||
className={cn(
|
|
||||||
`flex h-10 w-full border-none bg-gray-50 dark:bg-zinc-800 text-black dark:text-white shadow-input rounded-md px-3 py-2 text-sm file:border-0 file:bg-transparent
|
|
||||||
file:text-sm file:font-medium placeholder:text-neutral-400 dark:placeholder-text-neutral-600
|
|
||||||
focus-visible:outline-none focus-visible:ring-[2px] focus-visible:ring-neutral-400 dark:focus-visible:ring-neutral-600
|
|
||||||
disabled:cursor-not-allowed disabled:opacity-50
|
|
||||||
dark:shadow-[0px_0px_1px_1px_var(--neutral-700)]
|
|
||||||
group-hover/input:shadow-none transition duration-400
|
|
||||||
`,
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
ref={ref}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
StyledInput.displayName = "Input";
|
|
||||||
|
|
||||||
export { StyledInput };
|
|
@@ -1,11 +1,13 @@
|
|||||||
import { type Config } from "tailwindcss";
|
import { type Config } from "tailwindcss";
|
||||||
import sharedConfig from "@unsend/tailwind-config/tailwind.config";
|
import sharedConfig from "@usesend/tailwind-config/tailwind.config";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...sharedConfig,
|
...sharedConfig,
|
||||||
content: [
|
content: [
|
||||||
"./src/**/*.tsx",
|
"./src/**/*.tsx",
|
||||||
`${path.join(require.resolve("@unsend/ui"), "..")}/**/*.{ts,tsx}`,
|
`${path.join(require.resolve("@usesend/ui"), "..")}/**/*.{ts,tsx}`,
|
||||||
|
`${path.join(require.resolve("@usesend/email-editor"), "..")}/**/*.{ts,tsx}`,
|
||||||
],
|
],
|
||||||
} satisfies Config;
|
} satisfies Config;
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"extends": "@unsend/typescript-config/nextjs.json",
|
"extends": "@usesend/typescript-config/nextjs.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
@@ -18,7 +18,9 @@
|
|||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
"**/*.cjs",
|
"**/*.cjs",
|
||||||
"**/*.js",
|
"**/*.js",
|
||||||
".next/types/**/*.ts"
|
".next/types/**/*.ts",
|
||||||
|
".eslintrc.cjs"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
name: unsend-smtp-server
|
name: usesend-smtp-server
|
||||||
|
|
||||||
services:
|
services:
|
||||||
smtp-server:
|
smtp-server:
|
||||||
container_name: unsend-smtp-server
|
container_name: usesend-smtp-server
|
||||||
image: unsend/smtp-proxy:latest
|
image: usesend/smtp-proxy:latest
|
||||||
# Pass necessary environment variables
|
# Pass necessary environment variables
|
||||||
environment:
|
environment:
|
||||||
SMTP_AUTH_USERNAME: "unsend" # can be anything, just use the same while sending emails
|
SMTP_AUTH_USERNAME: "usesend" # can be anything, just use the same while sending emails
|
||||||
UNSEND_BASE_URL: "https://app.unsend.dev" # your self hosted unsend instance url
|
USESEND_BASE_URL: "https://app.usesend.com" # your self hosted useSend instance url
|
||||||
|
|
||||||
# Uncomment this if you have SSL certificates. port 465 and 2465 will be using SSL
|
# Uncomment this if you have SSL certificates. port 465 and 2465 will be using SSL
|
||||||
# UNSEND_API_KEY_PATH: "/certs/server.key"
|
# USESEND_API_KEY_PATH: "/certs/server.key"
|
||||||
# UNSEND_API_CERT_PATH: "/certs/server.crt"
|
# USESEND_API_CERT_PATH: "/certs/server.crt"
|
||||||
# If you have SSL certificates, mount them here (read-only recommended)
|
# If you have SSL certificates, mount them here (read-only recommended)
|
||||||
|
|
||||||
# volumes:
|
# volumes:
|
||||||
|
@@ -6,16 +6,21 @@ import { readFileSync, watch, FSWatcher } from "fs";
|
|||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const AUTH_USERNAME = process.env.SMTP_AUTH_USERNAME ?? "unsend";
|
const AUTH_USERNAME = process.env.SMTP_AUTH_USERNAME ?? "usesend";
|
||||||
const UNSEND_BASE_URL = process.env.UNSEND_BASE_URL ?? "https://app.unsend.dev";
|
const BASE_URL =
|
||||||
const SSL_KEY_PATH = process.env.UNSEND_API_KEY_PATH;
|
process.env.USESEND_BASE_URL ??
|
||||||
const SSL_CERT_PATH = process.env.UNSEND_API_CERT_PATH;
|
process.env.UNSEND_BASE_URL ??
|
||||||
|
"https://app.usesend.com";
|
||||||
|
const SSL_KEY_PATH =
|
||||||
|
process.env.USESEND_API_KEY_PATH ?? process.env.UNSEND_API_KEY_PATH;
|
||||||
|
const SSL_CERT_PATH =
|
||||||
|
process.env.USESEND_API_CERT_PATH ?? process.env.UNSEND_API_CERT_PATH;
|
||||||
|
|
||||||
async function sendEmailToUnsend(emailData: any, apiKey: string) {
|
async function sendEmailToUseSend(emailData: any, apiKey: string) {
|
||||||
try {
|
try {
|
||||||
const apiEndpoint = "/api/v1/emails";
|
const apiEndpoint = "/api/v1/emails";
|
||||||
const url = new URL(apiEndpoint, UNSEND_BASE_URL); // Combine base URL with endpoint
|
const url = new URL(apiEndpoint, BASE_URL); // Combine base URL with endpoint
|
||||||
console.log("Sending email to Unsend API at:", url.href); // Debug statement
|
console.log("Sending email to useSend API at:", url.href); // Debug statement
|
||||||
|
|
||||||
const emailDataText = JSON.stringify(emailData);
|
const emailDataText = JSON.stringify(emailData);
|
||||||
|
|
||||||
@@ -31,17 +36,17 @@ async function sendEmailToUnsend(emailData: any, apiKey: string) {
|
|||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.text();
|
const errorData = await response.text();
|
||||||
console.error(
|
console.error(
|
||||||
"Unsend API error response: error:",
|
"useSend API error response: error:",
|
||||||
JSON.stringify(errorData, null, 4),
|
JSON.stringify(errorData, null, 4),
|
||||||
`\nemail data: ${emailDataText}`
|
`\nemail data: ${emailDataText}`,
|
||||||
);
|
);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to send email: ${errorData || "Unknown error from server"}`
|
`Failed to send email: ${errorData || "Unknown error from server"}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
console.log("Unsend API response:", responseData);
|
console.log("useSend API response:", responseData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.error("Error message:", error.message);
|
console.error("Error message:", error.message);
|
||||||
@@ -69,7 +74,7 @@ const serverOptions: SMTPServerOptions = {
|
|||||||
onData(
|
onData(
|
||||||
stream: Readable,
|
stream: Readable,
|
||||||
session: SMTPServerSession,
|
session: SMTPServerSession,
|
||||||
callback: (error?: Error) => void
|
callback: (error?: Error) => void,
|
||||||
) {
|
) {
|
||||||
console.log("Receiving email data..."); // Debug statement
|
console.log("Receiving email data..."); // Debug statement
|
||||||
simpleParser(stream, (err, parsed) => {
|
simpleParser(stream, (err, parsed) => {
|
||||||
@@ -96,7 +101,7 @@ const serverOptions: SMTPServerOptions = {
|
|||||||
replyTo: parsed.replyTo?.text,
|
replyTo: parsed.replyTo?.text,
|
||||||
};
|
};
|
||||||
|
|
||||||
sendEmailToUnsend(emailObject, session.user)
|
sendEmailToUseSend(emailObject, session.user)
|
||||||
.then(() => callback())
|
.then(() => callback())
|
||||||
.then(() => console.log("Email sent successfully to: ", emailObject.to))
|
.then(() => console.log("Email sent successfully to: ", emailObject.to))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -128,7 +133,7 @@ function startServers() {
|
|||||||
|
|
||||||
server.listen(port, () => {
|
server.listen(port, () => {
|
||||||
console.log(
|
console.log(
|
||||||
`Implicit SSL/TLS SMTP server is listening on port ${port}`
|
`Implicit SSL/TLS SMTP server is listening on port ${port}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@ const transporter = nodemailer.createTransport({
|
|||||||
port: 25,
|
port: 25,
|
||||||
secure: false,
|
secure: false,
|
||||||
auth: {
|
auth: {
|
||||||
user: "unsend",
|
user: "usesend",
|
||||||
pass: "us_123",
|
pass: "us_123",
|
||||||
},
|
},
|
||||||
tls: {
|
tls: {
|
||||||
@@ -17,8 +17,8 @@ const mailOptions = {
|
|||||||
to: "sender@example.com",
|
to: "sender@example.com",
|
||||||
from: "hello@example.com",
|
from: "hello@example.com",
|
||||||
subject: "Testing SMTP",
|
subject: "Testing SMTP",
|
||||||
html: "<strong>THIS IS USING SMTP,</strong><p>Unsend is the best open source sending platform<p><p>check out <a href='https://unsend.dev'>unsend.dev</a>",
|
html: "<strong>THIS IS USING SMTP,</strong><p>useSend is the best open source sending platform<p><p>check out <a href='https://usesend.com'>usesend.com</a>",
|
||||||
text: "hello,\n\nUnsend is the best open source sending platform",
|
text: "hello,\n\nuseSend is the best open source sending platform",
|
||||||
};
|
};
|
||||||
|
|
||||||
transporter.sendMail(mailOptions, (error, info) => {
|
transporter.sendMail(mailOptions, (error, info) => {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
/** @type {import("eslint").Linter.Config} */
|
/** @type {import("eslint").Linter.Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
extends: ["@unsend/eslint-config/next.js"],
|
extends: ["@usesend/eslint-config/next.js"],
|
||||||
parser: "@typescript-eslint/parser",
|
parser: "@typescript-eslint/parser",
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: true,
|
project: true,
|
||||||
|
@@ -34,8 +34,8 @@
|
|||||||
"@trpc/next": "^11.1.1",
|
"@trpc/next": "^11.1.1",
|
||||||
"@trpc/react-query": "^11.1.1",
|
"@trpc/react-query": "^11.1.1",
|
||||||
"@trpc/server": "^11.1.1",
|
"@trpc/server": "^11.1.1",
|
||||||
"@unsend/email-editor": "workspace:*",
|
"@usesend/email-editor": "workspace:*",
|
||||||
"@unsend/ui": "workspace:*",
|
"@usesend/ui": "workspace:*",
|
||||||
"bullmq": "^5.51.1",
|
"bullmq": "^5.51.1",
|
||||||
"chrono-node": "^2.8.0",
|
"chrono-node": "^2.8.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
"superjson": "^2.2.2",
|
"superjson": "^2.2.2",
|
||||||
"tldts": "^7.0.4",
|
"tldts": "^7.0.4",
|
||||||
"ua-parser-js": "^2.0.3",
|
"ua-parser-js": "^2.0.3",
|
||||||
"unsend": "workspace:*",
|
"usesend": "workspace:*",
|
||||||
"use-debounce": "^10.0.4",
|
"use-debounce": "^10.0.4",
|
||||||
"zod": "^3.24.3",
|
"zod": "^3.24.3",
|
||||||
"zustand": "^5.0.8"
|
"zustand": "^5.0.8"
|
||||||
@@ -82,10 +82,10 @@
|
|||||||
"@types/ua-parser-js": "^0.7.39",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.31.0",
|
"@typescript-eslint/eslint-plugin": "^8.31.0",
|
||||||
"@typescript-eslint/parser": "^8.31.0",
|
"@typescript-eslint/parser": "^8.31.0",
|
||||||
"@unsend/eslint-config": "workspace:*",
|
"@usesend/eslint-config": "workspace:*",
|
||||||
"@unsend/tailwind-config": "workspace:*",
|
"@usesend/tailwind-config": "workspace:*",
|
||||||
"@unsend/typescript-config": "workspace:*",
|
"@usesend/typescript-config": "workspace:*",
|
||||||
"eslint": "^9.25.1",
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-next": "^15.3.1",
|
"eslint-config-next": "^15.3.1",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
-- Add dkimSelector column (nullable) with default 'usesend'
|
||||||
|
ALTER TABLE "Domain" ADD COLUMN "dkimSelector" TEXT;
|
||||||
|
|
||||||
|
-- Set existing rows to 'unsend' to preserve current selector
|
||||||
|
UPDATE "Domain" SET "dkimSelector" = 'unsend' WHERE "dkimSelector" IS NULL;
|
||||||
|
|
||||||
|
-- Set default for new rows to 'usesend'
|
||||||
|
ALTER TABLE "Domain" ALTER COLUMN "dkimSelector" SET DEFAULT 'usesend';
|
||||||
|
|
@@ -181,6 +181,7 @@ model Domain {
|
|||||||
clickTracking Boolean @default(false)
|
clickTracking Boolean @default(false)
|
||||||
openTracking Boolean @default(false)
|
openTracking Boolean @default(false)
|
||||||
publicKey String
|
publicKey String
|
||||||
|
dkimSelector String? @default("usesend")
|
||||||
dkimStatus String?
|
dkimStatus String?
|
||||||
spfDetails String?
|
spfDetails String?
|
||||||
dmarcAdded Boolean @default(false)
|
dmarcAdded Boolean @default(false)
|
||||||
|
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 419 B After Width: | Height: | Size: 292 B |
Before Width: | Height: | Size: 901 B After Width: | Height: | Size: 475 B |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 23 KiB |
@@ -1,14 +0,0 @@
|
|||||||
<svg width="650" height="650" viewBox="0 0 650 650" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<mask id="mask0_43_2" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="650" height="650">
|
|
||||||
<path d="M0 200C0 105.719 0 58.5786 29.2893 29.2893C58.5786 0 105.719 0 200 0H450C544.281 0 591.421 0 620.711 29.2893C650 58.5786 650 105.719 650 200V450C650 544.281 650 591.421 620.711 620.711C591.421 650 544.281 650 450 650H200C105.719 650 58.5786 650 29.2893 620.711C0 591.421 0 544.281 0 450V200Z" fill="black"/>
|
|
||||||
</mask>
|
|
||||||
<g mask="url(#mask0_43_2)">
|
|
||||||
<path d="M0 200C0 105.719 0 58.5786 29.2893 29.2893C58.5786 0 105.719 0 200 0H450C544.281 0 591.421 0 620.711 29.2893C650 58.5786 650 105.719 650 200V450C650 544.281 650 591.421 620.711 620.711C591.421 650 544.281 650 450 650H200C105.719 650 58.5786 650 29.2893 620.711C0 591.421 0 544.281 0 450V200Z" fill="white"/>
|
|
||||||
<path d="M431.817 203.253C435.698 188.755 437.639 181.506 441.477 181.506C445.314 181.506 447.255 188.755 451.136 203.253L481.757 317.625C488.908 344.332 492.483 357.686 488.449 359.37C484.415 361.055 477.433 349.124 463.467 325.262L450.107 302.435C446.265 295.87 444.344 292.588 441.477 292.588C438.61 292.588 436.688 295.87 432.846 302.435L419.486 325.262C405.521 349.124 398.538 361.055 394.504 359.37C390.47 357.686 394.046 344.332 401.196 317.625L431.817 203.253Z" fill="black"/>
|
|
||||||
<path d="M467.077 364.883C467.077 385.571 463.002 406.057 455.085 425.17C447.168 444.284 435.564 461.651 420.935 476.28C406.306 490.908 388.939 502.513 369.826 510.43C350.712 518.347 330.227 522.422 309.538 522.422C288.85 522.422 268.365 518.347 249.251 510.43C230.138 502.513 212.771 490.908 198.142 476.28C183.513 461.651 171.909 444.284 163.992 425.17C156.075 406.057 152 385.571 152 364.883L202.528 364.883C202.528 378.936 205.296 392.851 210.674 405.834C216.052 418.817 223.934 430.614 233.871 440.551C243.808 450.488 255.604 458.37 268.587 463.748C281.571 469.125 295.486 471.893 309.538 471.893C323.591 471.893 337.506 469.125 350.489 463.748C363.473 458.37 375.269 450.488 385.206 440.551C395.143 430.614 403.025 418.817 408.403 405.834C413.781 392.851 416.549 378.936 416.549 364.883H467.077Z" fill="black"/>
|
|
||||||
<path d="M152 156.145C152 142.224 163.285 130.938 177.206 130.938V130.938C191.127 130.938 202.412 142.224 202.412 156.145V366.458C202.412 380.379 191.127 391.665 177.206 391.665V391.665C163.285 391.665 152 380.379 152 366.458V156.145Z" fill="black"/>
|
|
||||||
<path d="M416.665 152.206C416.665 138.285 427.95 127 441.871 127V127C455.792 127 467.077 138.285 467.077 152.206V362.52C467.077 376.441 455.792 387.726 441.871 387.726V387.726C427.95 387.726 416.665 376.441 416.665 362.52V152.206Z" fill="black"/>
|
|
||||||
<path d="M419.676 139.67C421.558 133.877 427.78 130.708 433.572 132.59V132.59C439.364 134.472 442.534 140.693 440.652 146.485L366.899 373.475C365.017 379.267 358.795 382.437 353.003 380.555V380.555C347.211 378.673 344.041 372.452 345.923 366.659L419.676 139.67Z" fill="black"/>
|
|
||||||
<path d="M442.916 147.452C441.034 141.66 444.203 135.439 449.996 133.556V133.556C455.788 131.674 462.009 134.844 463.891 140.637L537.645 367.626C539.527 373.418 536.357 379.64 530.565 381.522V381.522C524.772 383.404 518.551 380.234 516.669 374.442L442.916 147.452Z" fill="black"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 89 KiB |
Before Width: | Height: | Size: 23 KiB |
@@ -1,14 +0,0 @@
|
|||||||
<svg width="650" height="650" viewBox="0 0 650 650" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<mask id="mask0_43_13" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="650" height="650">
|
|
||||||
<path d="M0 200C0 105.719 0 58.5786 29.2893 29.2893C58.5786 0 105.719 0 200 0H450C544.281 0 591.421 0 620.711 29.2893C650 58.5786 650 105.719 650 200V450C650 544.281 650 591.421 620.711 620.711C591.421 650 544.281 650 450 650H200C105.719 650 58.5786 650 29.2893 620.711C0 591.421 0 544.281 0 450V200Z" fill="black"/>
|
|
||||||
</mask>
|
|
||||||
<g mask="url(#mask0_43_13)">
|
|
||||||
<path d="M0 200C0 105.719 0 58.5786 29.2893 29.2893C58.5786 0 105.719 0 200 0H450C544.281 0 591.421 0 620.711 29.2893C650 58.5786 650 105.719 650 200V450C650 544.281 650 591.421 620.711 620.711C591.421 650 544.281 650 450 650H200C105.719 650 58.5786 650 29.2893 620.711C0 591.421 0 544.281 0 450V200Z" fill="black"/>
|
|
||||||
<path d="M431.817 203.253C435.698 188.755 437.639 181.506 441.477 181.506C445.314 181.506 447.255 188.755 451.136 203.253L481.757 317.625C488.908 344.332 492.483 357.686 488.449 359.37C484.415 361.055 477.433 349.124 463.467 325.262L450.107 302.435C446.265 295.87 444.344 292.588 441.477 292.588C438.61 292.588 436.688 295.87 432.846 302.435L419.486 325.262C405.521 349.124 398.538 361.055 394.504 359.37C390.47 357.686 394.046 344.332 401.196 317.625L431.817 203.253Z" fill="white"/>
|
|
||||||
<path d="M467.077 364.883C467.077 385.571 463.002 406.057 455.085 425.17C447.168 444.284 435.564 461.651 420.935 476.28C406.306 490.908 388.939 502.513 369.826 510.43C350.712 518.347 330.227 522.422 309.538 522.422C288.85 522.422 268.365 518.347 249.251 510.43C230.138 502.513 212.771 490.908 198.142 476.28C183.513 461.651 171.909 444.284 163.992 425.17C156.075 406.057 152 385.571 152 364.883L202.528 364.883C202.528 378.936 205.296 392.851 210.674 405.834C216.052 418.817 223.934 430.614 233.871 440.551C243.808 450.488 255.604 458.37 268.587 463.748C281.571 469.125 295.486 471.893 309.538 471.893C323.591 471.893 337.506 469.125 350.489 463.748C363.473 458.37 375.269 450.488 385.206 440.551C395.143 430.614 403.025 418.817 408.403 405.834C413.781 392.851 416.549 378.936 416.549 364.883H467.077Z" fill="white"/>
|
|
||||||
<path d="M152 156.145C152 142.224 163.285 130.938 177.206 130.938V130.938C191.127 130.938 202.412 142.224 202.412 156.145V366.458C202.412 380.379 191.127 391.665 177.206 391.665V391.665C163.285 391.665 152 380.379 152 366.458V156.145Z" fill="white"/>
|
|
||||||
<path d="M416.665 152.206C416.665 138.285 427.95 127 441.871 127V127C455.792 127 467.077 138.285 467.077 152.206V362.52C467.077 376.441 455.792 387.726 441.871 387.726V387.726C427.95 387.726 416.665 376.441 416.665 362.52V152.206Z" fill="white"/>
|
|
||||||
<path d="M419.676 139.67C421.558 133.877 427.78 130.708 433.572 132.59V132.59C439.364 134.472 442.534 140.693 440.652 146.485L366.899 373.475C365.017 379.267 358.795 382.437 353.003 380.555V380.555C347.211 378.673 344.041 372.452 345.923 366.659L419.676 139.67Z" fill="white"/>
|
|
||||||
<path d="M442.916 147.452C441.034 141.66 444.203 135.439 449.996 133.556V133.556C455.788 131.674 462.009 134.844 463.891 140.637L537.645 367.626C539.527 373.418 536.357 379.64 530.565 381.522V381.522C524.772 383.404 518.551 380.234 516.669 374.442L442.916 147.452Z" fill="white"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.2 KiB |
BIN
apps/web/public/logo-squircle.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
@@ -1,13 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@unsend/ui/src/dialog";
|
} from "@usesend/ui/src/dialog";
|
||||||
|
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@unsend/ui/src/dialog";
|
} from "@usesend/ui/src/dialog";
|
||||||
|
|
||||||
import { Edit } from "lucide-react";
|
import { Edit } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -20,13 +20,13 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@unsend/ui/src/form";
|
} from "@usesend/ui/src/form";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import { Input } from "@unsend/ui/src/input";
|
import { Input } from "@usesend/ui/src/input";
|
||||||
import { toast } from "@unsend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import Spinner from "@unsend/ui/src/spinner";
|
import Spinner from "@usesend/ui/src/spinner";
|
||||||
import { SesSetting } from "@prisma/client";
|
import { SesSetting } from "@prisma/client";
|
||||||
|
|
||||||
const FormSchema = z.object({
|
const FormSchema = z.object({
|
||||||
|
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
import AddSesConfiguration from "./add-ses-configuration";
|
import AddSesConfiguration from "./add-ses-configuration";
|
||||||
import SesConfigurations from "./ses-configurations";
|
import SesConfigurations from "./ses-configurations";
|
||||||
|
import { H1 } from "@usesend/ui";
|
||||||
|
|
||||||
export default function ApiKeysPage() {
|
export default function ApiKeysPage() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h1 className="font-bold text-lg">Admin</h1>
|
<H1>Admin</H1>
|
||||||
<AddSesConfiguration />
|
<AddSesConfiguration />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-10">
|
<div className="mt-10">
|
||||||
|
@@ -7,12 +7,12 @@ import {
|
|||||||
TableHead,
|
TableHead,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@unsend/ui/src/table";
|
} from "@usesend/ui/src/table";
|
||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import Spinner from "@unsend/ui/src/spinner";
|
import Spinner from "@usesend/ui/src/spinner";
|
||||||
import EditSesConfiguration from "./edit-ses-configuration";
|
import EditSesConfiguration from "./edit-ses-configuration";
|
||||||
import { TextWithCopyButton } from "@unsend/ui/src/text-with-copy";
|
import { TextWithCopyButton } from "@usesend/ui/src/text-with-copy";
|
||||||
|
|
||||||
export default function SesConfigurations() {
|
export default function SesConfigurations() {
|
||||||
const sesSettingsQuery = api.admin.getSesSettings.useQuery();
|
const sesSettingsQuery = api.admin.getSesSettings.useQuery();
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import { Spinner } from "@unsend/ui/src/spinner";
|
import { Spinner } from "@usesend/ui/src/spinner";
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@unsend/ui/src/input";
|
import { Input } from "@usesend/ui/src/input";
|
||||||
import { Editor } from "@unsend/email-editor";
|
import { Editor } from "@usesend/email-editor";
|
||||||
import { use, useState } from "react";
|
import { use, useState } from "react";
|
||||||
import { Campaign } from "@prisma/client";
|
import { Campaign } from "@prisma/client";
|
||||||
import {
|
import {
|
||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
} from "@unsend/ui/src/select";
|
} from "@usesend/ui/src/select";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@unsend/ui/src/dialog";
|
} from "@usesend/ui/src/dialog";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@@ -31,8 +31,8 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@unsend/ui/src/form";
|
} from "@usesend/ui/src/form";
|
||||||
import { toast } from "@unsend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import {
|
import {
|
||||||
@@ -40,7 +40,7 @@ import {
|
|||||||
AccordionContent,
|
AccordionContent,
|
||||||
AccordionItem,
|
AccordionItem,
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "@unsend/ui/src/accordion";
|
} from "@usesend/ui/src/accordion";
|
||||||
|
|
||||||
const sendSchema = z.object({
|
const sendSchema = z.object({
|
||||||
confirmation: z.string(),
|
confirmation: z.string(),
|
||||||
@@ -63,7 +63,7 @@ export default function EditCampaignPage({
|
|||||||
{ campaignId },
|
{ campaignId },
|
||||||
{
|
{
|
||||||
enabled: !!campaignId,
|
enabled: !!campaignId,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -98,7 +98,7 @@ function CampaignEditor({
|
|||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const [json, setJson] = useState<Record<string, any> | undefined>(
|
const [json, setJson] = useState<Record<string, any> | undefined>(
|
||||||
campaign.content ? JSON.parse(campaign.content) : undefined
|
campaign.content ? JSON.parse(campaign.content) : undefined,
|
||||||
);
|
);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [name, setName] = useState(campaign.name);
|
const [name, setName] = useState(campaign.name);
|
||||||
@@ -106,10 +106,10 @@ function CampaignEditor({
|
|||||||
const [from, setFrom] = useState(campaign.from);
|
const [from, setFrom] = useState(campaign.from);
|
||||||
const [contactBookId, setContactBookId] = useState(campaign.contactBookId);
|
const [contactBookId, setContactBookId] = useState(campaign.contactBookId);
|
||||||
const [replyTo, setReplyTo] = useState<string | undefined>(
|
const [replyTo, setReplyTo] = useState<string | undefined>(
|
||||||
campaign.replyTo[0]
|
campaign.replyTo[0],
|
||||||
);
|
);
|
||||||
const [previewText, setPreviewText] = useState<string | null>(
|
const [previewText, setPreviewText] = useState<string | null>(
|
||||||
campaign.previewText
|
campaign.previewText,
|
||||||
);
|
);
|
||||||
const [openSendDialog, setOpenSendDialog] = useState(false);
|
const [openSendDialog, setOpenSendDialog] = useState(false);
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ function CampaignEditor({
|
|||||||
|
|
||||||
const deboucedUpdateCampaign = useDebouncedCallback(
|
const deboucedUpdateCampaign = useDebouncedCallback(
|
||||||
updateEditorContent,
|
updateEditorContent,
|
||||||
1000
|
1000,
|
||||||
);
|
);
|
||||||
|
|
||||||
async function onSendCampaign(values: z.infer<typeof sendSchema>) {
|
async function onSendCampaign(values: z.infer<typeof sendSchema>) {
|
||||||
@@ -160,14 +160,14 @@ function CampaignEditor({
|
|||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(`Failed to send campaign: ${error.message}`);
|
toast.error(`Failed to send campaign: ${error.message}`);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFileChange = async (file: File) => {
|
const handleFileChange = async (file: File) => {
|
||||||
if (file.size > IMAGE_SIZE_LIMIT) {
|
if (file.size > IMAGE_SIZE_LIMIT) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`File should be less than ${IMAGE_SIZE_LIMIT / 1024 / 1024}MB`
|
`File should be less than ${IMAGE_SIZE_LIMIT / 1024 / 1024}MB`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ function CampaignEditor({
|
|||||||
const confirmation = sendForm.watch("confirmation");
|
const confirmation = sendForm.watch("confirmation");
|
||||||
|
|
||||||
const contactBook = contactBooksQuery.data?.find(
|
const contactBook = contactBooksQuery.data?.find(
|
||||||
(book) => book.id === contactBookId
|
(book) => book.id === contactBookId,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -220,7 +220,7 @@ function CampaignEditor({
|
|||||||
toast.error(`${e.message}. Reverting changes.`);
|
toast.error(`${e.message}. Reverting changes.`);
|
||||||
setName(campaign.name);
|
setName(campaign.name);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -315,7 +315,7 @@ function CampaignEditor({
|
|||||||
toast.error(`${e.message}. Reverting changes.`);
|
toast.error(`${e.message}. Reverting changes.`);
|
||||||
setSubject(campaign.subject);
|
setSubject(campaign.subject);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className="mt-1 py-1 text-sm block w-full outline-none border-b border-transparent focus:border-border bg-transparent"
|
className="mt-1 py-1 text-sm block w-full outline-none border-b border-transparent focus:border-border bg-transparent"
|
||||||
@@ -350,7 +350,7 @@ function CampaignEditor({
|
|||||||
toast.error(`${e.message}. Reverting changes.`);
|
toast.error(`${e.message}. Reverting changes.`);
|
||||||
setFrom(campaign.from);
|
setFrom(campaign.from);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -381,7 +381,7 @@ function CampaignEditor({
|
|||||||
toast.error(`${e.message}. Reverting changes.`);
|
toast.error(`${e.message}. Reverting changes.`);
|
||||||
setReplyTo(campaign.replyTo[0]);
|
setReplyTo(campaign.replyTo[0]);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -414,7 +414,7 @@ function CampaignEditor({
|
|||||||
toast.error(`${e.message}. Reverting changes.`);
|
toast.error(`${e.message}. Reverting changes.`);
|
||||||
setPreviewText(campaign.previewText ?? "");
|
setPreviewText(campaign.previewText ?? "");
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className="mt-1 py-1 text-sm block w-full outline-none border-b border-transparent bg-transparent focus:border-border"
|
className="mt-1 py-1 text-sm block w-full outline-none border-b border-transparent bg-transparent focus:border-border"
|
||||||
@@ -440,7 +440,7 @@ function CampaignEditor({
|
|||||||
onError: () => {
|
onError: () => {
|
||||||
setContactBookId(campaign.contactBookId);
|
setContactBookId(campaign.contactBookId);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
setContactBookId(val);
|
setContactBookId(val);
|
||||||
}}
|
}}
|
||||||
|
@@ -7,10 +7,11 @@ import {
|
|||||||
BreadcrumbList,
|
BreadcrumbList,
|
||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from "@unsend/ui/src/breadcrumb";
|
} from "@usesend/ui/src/breadcrumb";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { H2 } from "@usesend/ui";
|
||||||
|
|
||||||
import Spinner from "@unsend/ui/src/spinner";
|
import Spinner from "@usesend/ui/src/spinner";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import { use } from "react";
|
import { use } from "react";
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ export default function CampaignDetailsPage({
|
|||||||
</BreadcrumbList>
|
</BreadcrumbList>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="mt-10">
|
<div className="mt-10">
|
||||||
<h2 className="text-xl font-semibold mb-4"> Statistics</h2>
|
<H2 className="mb-4"> Statistics</H2>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
{statusCards.map((card) => (
|
{statusCards.map((card) => (
|
||||||
<div
|
<div
|
||||||
@@ -110,7 +111,7 @@ export default function CampaignDetailsPage({
|
|||||||
|
|
||||||
{campaign.html && (
|
{campaign.html && (
|
||||||
<div className=" rounded-lg mt-16">
|
<div className=" rounded-lg mt-16">
|
||||||
<h2 className="text-xl font-semibold mb-4">Email</h2>
|
<H2 className="mb-4">Email</H2>
|
||||||
|
|
||||||
<div className="p-2 rounded-lg border shadow flex flex-col gap-4 w-full">
|
<div className="p-2 rounded-lg border shadow flex flex-col gap-4 w-full">
|
||||||
<div className="flex flex-col gap-3 px-4 py-1">
|
<div className="flex flex-col gap-3 px-4 py-1">
|
||||||
|
@@ -7,11 +7,11 @@ import {
|
|||||||
TableHead,
|
TableHead,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
} from "@unsend/ui/src/table";
|
} from "@usesend/ui/src/table";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import { useUrlState } from "~/hooks/useUrlState";
|
import { useUrlState } from "~/hooks/useUrlState";
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import Spinner from "@unsend/ui/src/spinner";
|
import Spinner from "@usesend/ui/src/spinner";
|
||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import { CampaignStatus } from "@prisma/client";
|
import { CampaignStatus } from "@prisma/client";
|
||||||
import DeleteCampaign from "./delete-campaign";
|
import DeleteCampaign from "./delete-campaign";
|
||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
} from "@unsend/ui/src/select";
|
} from "@usesend/ui/src/select";
|
||||||
|
|
||||||
export default function CampaignList() {
|
export default function CampaignList() {
|
||||||
const [page, setPage] = useUrlState("page", "1");
|
const [page, setPage] = useUrlState("page", "1");
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@unsend/ui/src/input";
|
import { Input } from "@usesend/ui/src/input";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@unsend/ui/src/dialog";
|
} from "@usesend/ui/src/dialog";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@unsend/ui/src/form";
|
} from "@usesend/ui/src/form";
|
||||||
|
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -24,9 +24,9 @@ import { Plus } from "lucide-react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { toast } from "@unsend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import Spinner from "@unsend/ui/src/spinner";
|
import Spinner from "@usesend/ui/src/spinner";
|
||||||
|
|
||||||
const campaignSchema = z.object({
|
const campaignSchema = z.object({
|
||||||
name: z.string({ required_error: "Name is required" }).min(1, {
|
name: z.string({ required_error: "Name is required" }).min(1, {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@unsend/ui/src/input";
|
import { Input } from "@usesend/ui/src/input";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -9,10 +9,10 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@unsend/ui/src/dialog";
|
} from "@usesend/ui/src/dialog";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { toast } from "@unsend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { Trash2 } from "lucide-react";
|
import { Trash2 } from "lucide-react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@unsend/ui/src/form";
|
} from "@usesend/ui/src/form";
|
||||||
import { Campaign } from "@prisma/client";
|
import { Campaign } from "@prisma/client";
|
||||||
|
|
||||||
const campaignSchema = z.object({
|
const campaignSchema = z.object({
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -8,10 +8,10 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@unsend/ui/src/dialog";
|
} from "@usesend/ui/src/dialog";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { toast } from "@unsend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { Copy } from "lucide-react";
|
import { Copy } from "lucide-react";
|
||||||
import { Campaign } from "@prisma/client";
|
import { Campaign } from "@prisma/client";
|
||||||
|
|
||||||
|
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
import CampaignList from "./campaign-list";
|
import CampaignList from "./campaign-list";
|
||||||
import CreateCampaign from "./create-campaign";
|
import CreateCampaign from "./create-campaign";
|
||||||
|
import { H1 } from "@usesend/ui";
|
||||||
|
|
||||||
export default function ContactsPage() {
|
export default function ContactsPage() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h1 className="font-bold text-lg">Campaigns</h1>
|
<H1>Campaigns</H1>
|
||||||
<CreateCampaign />
|
<CreateCampaign />
|
||||||
</div>
|
</div>
|
||||||
<CampaignList />
|
<CampaignList />
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Textarea } from "@unsend/ui/src/textarea";
|
import { Textarea } from "@usesend/ui/src/textarea";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@unsend/ui/src/dialog";
|
} from "@usesend/ui/src/dialog";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@unsend/ui/src/form";
|
} from "@usesend/ui/src/form";
|
||||||
|
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -26,7 +26,7 @@ import { useRouter } from "next/navigation";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { toast } from "@unsend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
|
|
||||||
const contactsSchema = z.object({
|
const contactsSchema = z.object({
|
||||||
contacts: z.string({ required_error: "Contacts are required" }).min(1, {
|
contacts: z.string({ required_error: "Contacts are required" }).min(1, {
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
} from "@unsend/ui/src/select";
|
} from "@usesend/ui/src/select";
|
||||||
import Spinner from "@unsend/ui/src/spinner";
|
import Spinner from "@usesend/ui/src/spinner";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
TableHead,
|
TableHead,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@unsend/ui/src/table";
|
} from "@usesend/ui/src/table";
|
||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useUrlState } from "~/hooks/useUrlState";
|
import { useUrlState } from "~/hooks/useUrlState";
|
||||||
@@ -23,14 +23,14 @@ import { api } from "~/trpc/react";
|
|||||||
import { getGravatarUrl } from "~/utils/gravatar-utils";
|
import { getGravatarUrl } from "~/utils/gravatar-utils";
|
||||||
import DeleteContact from "./delete-contact";
|
import DeleteContact from "./delete-contact";
|
||||||
import EditContact from "./edit-contact";
|
import EditContact from "./edit-contact";
|
||||||
import { Input } from "@unsend/ui/src/input";
|
import { Input } from "@usesend/ui/src/input";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@unsend/ui/src/tooltip";
|
} from "@usesend/ui/src/tooltip";
|
||||||
import { UnsubscribeReason } from "@prisma/client";
|
import { UnsubscribeReason } from "@prisma/client";
|
||||||
|
|
||||||
function getUnsubscribeReason(reason: UnsubscribeReason) {
|
function getUnsubscribeReason(reason: UnsubscribeReason) {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@unsend/ui/src/input";
|
import { Input } from "@usesend/ui/src/input";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -9,10 +9,10 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@unsend/ui/src/dialog";
|
} from "@usesend/ui/src/dialog";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { toast } from "@unsend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { Trash2 } from "lucide-react";
|
import { Trash2 } from "lucide-react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@unsend/ui/src/form";
|
} from "@usesend/ui/src/form";
|
||||||
import { Contact } from "@prisma/client";
|
import { Contact } from "@prisma/client";
|
||||||
|
|
||||||
const contactSchema = z.object({
|
const contactSchema = z.object({
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@unsend/ui/src/input";
|
import { Input } from "@usesend/ui/src/input";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@unsend/ui/src/dialog";
|
} from "@usesend/ui/src/dialog";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@unsend/ui/src/form";
|
} from "@usesend/ui/src/form";
|
||||||
|
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -26,8 +26,8 @@ import { useRouter } from "next/navigation";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { toast } from "@unsend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { Switch } from "@unsend/ui/src/switch";
|
import { Switch } from "@usesend/ui/src/switch";
|
||||||
import { Contact } from "@prisma/client";
|
import { Contact } from "@prisma/client";
|
||||||
|
|
||||||
const contactSchema = z.object({
|
const contactSchema = z.object({
|
||||||
|
@@ -8,20 +8,20 @@ import {
|
|||||||
BreadcrumbList,
|
BreadcrumbList,
|
||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from "@unsend/ui/src/breadcrumb";
|
} from "@usesend/ui/src/breadcrumb";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import AddContact from "./add-contact";
|
import AddContact from "./add-contact";
|
||||||
import ContactList from "./contact-list";
|
import ContactList from "./contact-list";
|
||||||
import { TextWithCopyButton } from "@unsend/ui/src/text-with-copy";
|
import { TextWithCopyButton } from "@usesend/ui/src/text-with-copy";
|
||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import EmojiPicker, { Theme } from "emoji-picker-react";
|
import EmojiPicker, { Theme } from "emoji-picker-react";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@unsend/ui/src/popover";
|
} from "@usesend/ui/src/popover";
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { useTheme } from "@unsend/ui";
|
import { useTheme } from "@usesend/ui";
|
||||||
import { use } from "react";
|
import { use } from "react";
|
||||||
|
|
||||||
export default function ContactsPage({
|
export default function ContactsPage({
|
||||||
@@ -51,7 +51,7 @@ export default function ContactsPage({
|
|||||||
...old,
|
...old,
|
||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
|
@@ -1,19 +1,19 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@unsend/ui/src/input";
|
import { Input } from "@usesend/ui/src/input";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@unsend/ui/src/dialog";
|
} from "@usesend/ui/src/dialog";
|
||||||
|
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
import { toast } from "@unsend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@unsend/ui/src/form";
|
} from "@usesend/ui/src/form";
|
||||||
import { useUpgradeModalStore } from "~/store/upgradeModalStore";
|
import { useUpgradeModalStore } from "~/store/upgradeModalStore";
|
||||||
import { LimitReason } from "~/lib/constants/plans";
|
import { LimitReason } from "~/lib/constants/plans";
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ import EditContactBook from "./edit-contact-book";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { useUrlState } from "~/hooks/useUrlState";
|
import { useUrlState } from "~/hooks/useUrlState";
|
||||||
import { Input } from "@unsend/ui/src/input";
|
import { Input } from "@usesend/ui/src/input";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
|
||||||
export default function ContactBooksList() {
|
export default function ContactBooksList() {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@unsend/ui/src/input";
|
import { Input } from "@usesend/ui/src/input";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -9,10 +9,10 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@unsend/ui/src/dialog";
|
} from "@usesend/ui/src/dialog";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { toast } from "@unsend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { Trash2 } from "lucide-react";
|
import { Trash2 } from "lucide-react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@unsend/ui/src/form";
|
} from "@usesend/ui/src/form";
|
||||||
import { ContactBook } from "@prisma/client";
|
import { ContactBook } from "@prisma/client";
|
||||||
|
|
||||||
const contactBookSchema = z.object({
|
const contactBookSchema = z.object({
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@unsend/ui/src/input";
|
import { Input } from "@usesend/ui/src/input";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@unsend/ui/src/dialog";
|
} from "@usesend/ui/src/dialog";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -16,14 +16,14 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@unsend/ui/src/form";
|
} from "@usesend/ui/src/form";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Edit } from "lucide-react";
|
import { Edit } from "lucide-react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { toast } from "@unsend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
|
|
||||||
const contactBookSchema = z.object({
|
const contactBookSchema = z.object({
|
||||||
name: z.string().min(1, { message: "Name is required" }),
|
name: z.string().min(1, { message: "Name is required" }),
|
||||||
|
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
import AddContactBook from "./add-contact-book";
|
import AddContactBook from "./add-contact-book";
|
||||||
import ContactBooksList from "./contact-books-list";
|
import ContactBooksList from "./contact-books-list";
|
||||||
|
import { H1 } from "@usesend/ui";
|
||||||
|
|
||||||
export default function ContactsPage() {
|
export default function ContactsPage() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h1 className="font-semibold text-xl">Contact books</h1>
|
<H1>Contact books</H1>
|
||||||
<AddContactBook />
|
<AddContactBook />
|
||||||
</div>
|
</div>
|
||||||
<ContactBooksList />
|
<ContactBooksList />
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { AppSidebar } from "~/components/AppSideBar";
|
import { AppSidebar } from "~/components/AppSideBar";
|
||||||
import { SidebarInset, SidebarTrigger } from "@unsend/ui/src/sidebar";
|
import { SidebarInset, SidebarTrigger } from "@usesend/ui/src/sidebar";
|
||||||
import { SidebarProvider } from "@unsend/ui/src/sidebar";
|
import { SidebarProvider } from "@usesend/ui/src/sidebar";
|
||||||
import { useIsMobile } from "@unsend/ui/src/hooks/use-mobile";
|
import { useIsMobile } from "@usesend/ui/src/hooks/use-mobile";
|
||||||
import { UpgradeModal } from "~/components/payments/UpgradeModal";
|
import { UpgradeModal } from "~/components/payments/UpgradeModal";
|
||||||
|
|
||||||
export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@unsend/ui/src/tabs";
|
import { Tabs, TabsList, TabsTrigger } from "@usesend/ui/src/tabs";
|
||||||
import { useUrlState } from "~/hooks/useUrlState";
|
import { useUrlState } from "~/hooks/useUrlState";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
} from "@unsend/ui/src/select";
|
} from "@usesend/ui/src/select";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
|
|
||||||
interface DashboardFiltersProps {
|
interface DashboardFiltersProps {
|
||||||
|
@@ -13,8 +13,8 @@ import {
|
|||||||
import { EmailStatusIcon } from "../emails/email-status-badge";
|
import { EmailStatusIcon } from "../emails/email-status-badge";
|
||||||
import { EmailStatus } from "@prisma/client";
|
import { EmailStatus } from "@prisma/client";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import Spinner from "@unsend/ui/src/spinner";
|
import Spinner from "@usesend/ui/src/spinner";
|
||||||
import { useTheme } from "@unsend/ui";
|
import { useTheme } from "@usesend/ui";
|
||||||
import { useColors } from "./hooks/useColors";
|
import { useColors } from "./hooks/useColors";
|
||||||
|
|
||||||
interface EmailChartProps {
|
interface EmailChartProps {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { useTheme } from "@unsend/ui";
|
import { useTheme } from "@usesend/ui";
|
||||||
|
|
||||||
export function useColors() {
|
export function useColors() {
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import EmailChart from "./email-chart";
|
import EmailChart from "./email-chart";
|
||||||
import DashboardFilters from "./dashboard-filters";
|
import DashboardFilters from "./dashboard-filters";
|
||||||
|
import { H1 } from "@usesend/ui";
|
||||||
import { useUrlState } from "~/hooks/useUrlState";
|
import { useUrlState } from "~/hooks/useUrlState";
|
||||||
import { ReputationMetrics } from "./reputation-metrics";
|
import { ReputationMetrics } from "./reputation-metrics";
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ export default function Dashboard() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="flex justify-between items-center mb-10">
|
<div className="flex justify-between items-center mb-10">
|
||||||
<h1 className="font-semibold text-xl">Analytics</h1>
|
<H1>Analytics</H1>
|
||||||
<DashboardFilters
|
<DashboardFilters
|
||||||
days={days ?? "7"}
|
days={days ?? "7"}
|
||||||
setDays={setDays}
|
setDays={setDays}
|
||||||
|
@@ -3,7 +3,7 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@unsend/ui/src/tooltip";
|
} from "@usesend/ui/src/tooltip";
|
||||||
import {
|
import {
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
CheckCircle2Icon,
|
CheckCircle2Icon,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@usesend/ui/src/button";
|
||||||
import { Input } from "@unsend/ui/src/input";
|
import { Input } from "@usesend/ui/src/input";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -9,12 +9,12 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@unsend/ui/src/dialog";
|
} from "@usesend/ui/src/dialog";
|
||||||
|
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { CheckIcon, ClipboardCopy, Eye, EyeOff, Plus } from "lucide-react";
|
import { CheckIcon, ClipboardCopy, Eye, EyeOff, Plus } from "lucide-react";
|
||||||
import { toast } from "@unsend/ui/src/toaster";
|
import { toast } from "@usesend/ui/src/toaster";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@unsend/ui/src/form";
|
} from "@usesend/ui/src/form";
|
||||||
|
|
||||||
const apiKeySchema = z.object({
|
const apiKeySchema = z.object({
|
||||||
name: z.string({ required_error: "Name is required" }).min(1, {
|
name: z.string({ required_error: "Name is required" }).min(1, {
|
||||||
|
@@ -7,11 +7,11 @@ import {
|
|||||||
TableHead,
|
TableHead,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@unsend/ui/src/table";
|
} from "@usesend/ui/src/table";
|
||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import DeleteApiKey from "./delete-api-key";
|
import DeleteApiKey from "./delete-api-key";
|
||||||
import Spinner from "@unsend/ui/src/spinner";
|
import Spinner from "@usesend/ui/src/spinner";
|
||||||
|
|
||||||
export default function ApiList() {
|
export default function ApiList() {
|
||||||
const apiKeysQuery = api.apiKey.getApiKeys.useQuery();
|
const apiKeysQuery = api.apiKey.getApiKeys.useQuery();
|
||||||
|