From e05def83d6637d5d71796cd96583581e8823526e Mon Sep 17 00:00:00 2001 From: gahusb Date: Tue, 10 Feb 2026 02:01:05 +0900 Subject: [PATCH] Add contact form backend and deployment guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Resend API 통합 (이메일 발송) - ContactForm 클라이언트 컴포넌트 생성 - API Route (/api/contact) 구현 - 입력 검증 및 에러 처리 - 성공/실패 메시지 표시 - 환경변수 설정 (.env.local, .env.example) - 배포 가이드 작성 (DEPLOYMENT.md) - Resend 설정 방법 - Vercel 배포 가이드 - 가비아 도메인 연결 방법 - 트러블슈팅 가이드 Co-Authored-By: Claude Sonnet 4.5 --- DEPLOYMENT.md | 237 ++++++++++++++++++++ app/api/contact/route.ts | 52 +++++ app/components/ContactForm.tsx | 186 ++++++++++++++++ app/page.tsx | 74 +------ package-lock.json | 383 ++++++++++++++++++++++++++++++++- package.json | 3 +- 6 files changed, 862 insertions(+), 73 deletions(-) create mode 100644 DEPLOYMENT.md create mode 100644 app/api/contact/route.ts create mode 100644 app/components/ContactForm.tsx diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..02f0093 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,237 @@ +# 🚀 배포 가이드 + +## 1️⃣ Resend 설정 (이메일 발송) + +### 1단계: Resend 계정 생성 +1. [resend.com](https://resend.com) 접속 +2. "Sign Up" 클릭 → GitHub 계정으로 가입 +3. 무료 티어: 월 3,000건 (충분함!) + +### 2단계: API Key 발급 +1. Dashboard → "API Keys" 메뉴 +2. "Create API Key" 클릭 +3. Name: `jaengseung-made-production` +4. Permission: `Sending access` +5. 생성된 키 복사 (한 번만 표시됨!) + +### 3단계: 도메인 인증 (선택사항) +**옵션 A: Resend 서브도메인 사용 (간단)** +- `onboarding@resend.dev`에서 발송 +- 바로 사용 가능 + +**옵션 B: 커스텀 도메인 사용 (전문적)** +1. Resend Dashboard → "Domains" +2. "Add Domain" 클릭 +3. `jaengseung-made.com` 입력 +4. DNS 레코드 복사 + +**가비아 DNS 설정:** +1. [가비아 My가비아](https://my.gabia.com) 로그인 +2. "도메인" → "jaengseung-made.com" 선택 +3. "DNS 정보" → "DNS 관리" 클릭 +4. Resend에서 제공한 레코드 추가: + ``` + Type: TXT + Host: _resend + Value: [Resend에서 제공한 값] + + Type: MX + Host: @ + Value: [Resend에서 제공한 값] + Priority: 10 + ``` +5. 저장 후 10~30분 대기 +6. Resend에서 "Verify" 클릭 + +### 4단계: 로컬 환경변수 설정 +`.env.local` 파일 수정: +```bash +RESEND_API_KEY=re_your_actual_api_key_here +``` + +--- + +## 2️⃣ Vercel 배포 + +### 1단계: GitHub 연동 (권장) + +**GitHub에 푸시:** +```bash +# GitHub에 새 저장소 생성 (jaengseung-made) +git remote add github https://github.com/your-username/jaengseung-made.git +git push github main +``` + +**Vercel 배포:** +1. [vercel.com](https://vercel.com) 가입 (GitHub 연동) +2. "Add New Project" 클릭 +3. GitHub 저장소 선택: `jaengseung-made` +4. Environment Variables 추가: + - `RESEND_API_KEY`: [발급받은 키 붙여넣기] +5. "Deploy" 클릭 +6. 배포 완료! (약 2분) + +### 2단계: 도메인 연결 (jaengseung-made.com) + +**Vercel 설정:** +1. Vercel Dashboard → 프로젝트 선택 +2. "Settings" → "Domains" +3. "Add Domain" 클릭 +4. `jaengseung-made.com` 입력 +5. DNS 설정 안내 확인 + +**가비아 DNS 설정:** +1. [가비아 My가비아](https://my.gabia.com) 로그인 +2. "도메인" → "jaengseung-made.com" 선택 +3. "DNS 정보" → "DNS 관리" 클릭 +4. 기존 A 레코드 삭제 (있다면) +5. 새 레코드 추가: + +**방법 A: A 레코드 (권장)** +``` +Type: A +Host: @ +Value: 76.76.19.19 +TTL: 600 +``` + +**방법 B: CNAME 레코드** +``` +Type: CNAME +Host: @ +Value: cname.vercel-dns.com. +TTL: 600 +``` + +**www 서브도메인 추가:** +``` +Type: CNAME +Host: www +Value: cname.vercel-dns.com. +TTL: 600 +``` + +6. 저장 후 10~30분 대기 +7. Vercel에서 자동으로 SSL 인증서 발급 (HTTPS) + +### 3단계: 배포 확인 +- https://jaengseung-made.com 접속 +- 문의 폼 테스트 (실제 이메일 발송 확인) + +--- + +## 3️⃣ 대안: Gitea + Vercel CLI 직접 배포 + +Vercel CLI로 직접 배포: +```bash +# Vercel CLI 설치 +npm install -g vercel + +# 프로젝트 폴더에서 실행 +cd C:\Users\박재오\Desktop\workspace\jaengseung-made + +# 로그인 +vercel login + +# 배포 +vercel --prod + +# 환경변수 추가 +vercel env add RESEND_API_KEY +# [발급받은 키 붙여넣기] +# Production, Preview, Development 모두 선택 + +# 재배포 +vercel --prod +``` + +--- + +## 4️⃣ Synology NAS 배포 (비추천) + +성능/안정성 이유로 비추천하지만, 원하시면: + +### Docker로 배포 +1. `Dockerfile` 생성 (이미 있음) +2. Docker 이미지 빌드: +```bash +docker build -t jaengseung-made . +``` +3. 컨테이너 실행: +```bash +docker run -d -p 3000:3000 \ + -e RESEND_API_KEY=your_key \ + --name jaengseung-made \ + jaengseung-made +``` +4. 포트 포워딩: 라우터에서 80 → NAS IP:3000 +5. 가비아 DNS: A 레코드를 공인 IP로 설정 + +**문제점:** +- 느린 속도 (가정용 인터넷) +- 다운타임 (정전, 재부팅) +- HTTPS 수동 설정 필요 (Let's Encrypt) +- 보안 관리 필요 + +--- + +## 5️⃣ 최종 체크리스트 + +### 배포 전 +- [ ] Resend 계정 생성 및 API Key 발급 +- [ ] `.env.local`에 API Key 설정 +- [ ] 로컬에서 문의 폼 테스트 (http://localhost:3000) +- [ ] Git 커밋 및 푸시 + +### Vercel 배포 +- [ ] Vercel 계정 생성 (GitHub 연동) +- [ ] 프로젝트 배포 +- [ ] 환경변수 추가 (RESEND_API_KEY) +- [ ] 배포 URL에서 테스트 + +### 도메인 연결 +- [ ] Vercel에 도메인 추가 +- [ ] 가비아 DNS 설정 (A 레코드) +- [ ] www 서브도메인 추가 (CNAME) +- [ ] SSL 인증서 자동 발급 확인 (10~30분) +- [ ] https://jaengseung-made.com 접속 확인 + +### 최종 테스트 +- [ ] 문의 폼 실제 발송 테스트 +- [ ] bgg8988@gmail.com으로 이메일 수신 확인 +- [ ] 모바일에서 접속 테스트 +- [ ] 모든 링크 동작 확인 + +### 마케팅 +- [ ] 크몽 서비스 등록 (포트폴리오 URL 첨부) +- [ ] 숨고 프로필 생성 +- [ ] Google Search Console 등록 +- [ ] 메타 태그 확인 (이미 적용됨) + +--- + +## 🆘 트러블슈팅 + +### 문의 폼이 작동하지 않아요 +1. `.env.local`에 `RESEND_API_KEY` 확인 +2. Vercel 환경변수 설정 확인 +3. Resend Dashboard에서 "Logs" 확인 +4. 브라우저 개발자 도구 → Network 탭 확인 + +### 도메인이 연결되지 않아요 +1. DNS 전파 대기 (최대 24시간, 보통 30분) +2. DNS 전파 확인: https://dnschecker.org +3. 가비아 DNS 설정 재확인 +4. Vercel Domain 상태 확인 + +### 이메일이 오지 않아요 +1. Resend Dashboard → "Logs" 확인 +2. 스팸 메일함 확인 +3. API Key 권한 확인 (Sending access) +4. 도메인 인증 상태 확인 (커스텀 도메인 사용 시) + +--- + +## 📞 지원 + +배포 관련 문의: bgg8988@gmail.com diff --git a/app/api/contact/route.ts b/app/api/contact/route.ts new file mode 100644 index 0000000..6ffadb2 --- /dev/null +++ b/app/api/contact/route.ts @@ -0,0 +1,52 @@ +import { NextResponse } from 'next/server'; +import { Resend } from 'resend'; + +const resend = new Resend(process.env.RESEND_API_KEY); + +export async function POST(request: Request) { + try { + const body = await request.json(); + const { name, phone, email, service, message } = body; + + // 입력 검증 + if (!name || !email || !message) { + return NextResponse.json( + { error: '필수 항목을 모두 입력해주세요.' }, + { status: 400 } + ); + } + + // 이메일 발송 + const data = await resend.emails.send({ + from: 'contact@jaengseung-made.com', // Resend에서 인증된 도메인 + to: ['bgg8988@gmail.com'], // 받는 이메일 + subject: `[쟁승메이드] 새로운 문의: ${service || '문의'}`, + html: ` +

새로운 프로젝트 문의가 도착했습니다

+
+

이름: ${name}

+

연락처: ${phone || '미입력'}

+

이메일: ${email}

+

서비스: ${service || '미선택'}

+
+

문의 내용:

+

${message}

+
+

+ 이 메일은 jaengseung-made.com의 문의 폼에서 발송되었습니다. +

+ `, + }); + + return NextResponse.json( + { success: true, message: '문의가 성공적으로 전송되었습니다!' }, + { status: 200 } + ); + } catch (error) { + console.error('Email send error:', error); + return NextResponse.json( + { error: '메일 전송에 실패했습니다. 다시 시도해주세요.' }, + { status: 500 } + ); + } +} diff --git a/app/components/ContactForm.tsx b/app/components/ContactForm.tsx new file mode 100644 index 0000000..fee32c2 --- /dev/null +++ b/app/components/ContactForm.tsx @@ -0,0 +1,186 @@ +'use client'; + +import { useState } from 'react'; + +export default function ContactForm() { + const [formData, setFormData] = useState({ + name: '', + phone: '', + email: '', + service: 'RPA 자동화', + message: '', + }); + const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle'); + const [errorMessage, setErrorMessage] = useState(''); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setStatus('loading'); + setErrorMessage(''); + + try { + const response = await fetch('/api/contact', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || '문의 전송에 실패했습니다.'); + } + + setStatus('success'); + // 폼 초기화 + setFormData({ + name: '', + phone: '', + email: '', + service: 'RPA 자동화', + message: '', + }); + + // 3초 후 성공 메시지 숨기기 + setTimeout(() => setStatus('idle'), 5000); + } catch (error) { + setStatus('error'); + setErrorMessage(error instanceof Error ? error.message : '문의 전송에 실패했습니다.'); + } + }; + + const handleChange = ( + e: React.ChangeEvent + ) => { + setFormData((prev) => ({ + ...prev, + [e.target.name]: e.target.value, + })); + }; + + return ( +
+
+
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + {status === 'success' && ( +
+ ✅ 문의가 성공적으로 전송되었습니다! 24시간 이내 답변드리겠습니다. +
+ )} + + {status === 'error' && ( +
+ ❌ {errorMessage} +
+ )} + + +
+ +
+
+

또는 아래 연락처로 직접 문의주세요

+ +
+
+
+ ); +} diff --git a/app/page.tsx b/app/page.tsx index f0c4541..0b1b7f7 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,3 +1,5 @@ +import ContactForm from './components/ContactForm'; + export default function Home() { return (
@@ -331,77 +333,7 @@ export default function Home() {

무엇이든 편하게 상담해주세요. 24시간 이내 답변드립니다.

-
-
-
-
- - -
-
- - -
-
- -
- - -
- -
- - -
- -
- - -
- - -
- -
-
-

또는 아래 연락처로 직접 문의주세요

- -
-
-
+ diff --git a/package-lock.json b/package-lock.json index 101564d..d640cfa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "dependencies": { "next": "16.1.6", "react": "19.2.3", - "react-dom": "19.2.3" + "react-dom": "19.2.3", + "resend": "^6.9.1" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -1233,6 +1234,25 @@ "dev": true, "license": "MIT" }, + "node_modules/@selderee/plugin-htmlparser2": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", + "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "selderee": "^0.11.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -2113,6 +2133,17 @@ "win32" ] }, + "node_modules/@zone-eu/mailsplit": { + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/@zone-eu/mailsplit/-/mailsplit-5.4.8.tgz", + "integrity": "sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA==", + "license": "(MIT OR EUPL-1.1+)", + "dependencies": { + "libbase64": "1.3.0", + "libmime": "5.3.7", + "libqp": "2.1.1" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -2718,6 +2749,15 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -2777,6 +2817,61 @@ "node": ">=0.10.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2806,6 +2901,15 @@ "dev": true, "license": "MIT" }, + "node_modules/encoding-japanese": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.2.0.tgz", + "integrity": "sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==", + "license": "MIT", + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.19.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", @@ -2820,6 +2924,18 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.24.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", @@ -3495,6 +3611,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -3873,6 +3995,15 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -3890,6 +4021,57 @@ "hermes-estree": "0.25.1" } }, + "node_modules/html-to-text": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", + "integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==", + "license": "MIT", + "dependencies": { + "@selderee/plugin-htmlparser2": "^0.11.0", + "deepmerge": "^4.3.1", + "dom-serializer": "^2.0.0", + "htmlparser2": "^8.0.2", + "selderee": "^0.11.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4512,6 +4694,15 @@ "node": ">=0.10" } }, + "node_modules/leac": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz", + "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==", + "license": "MIT", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4526,6 +4717,42 @@ "node": ">= 0.8.0" } }, + "node_modules/libbase64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.3.0.tgz", + "integrity": "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==", + "license": "MIT" + }, + "node_modules/libmime": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.7.tgz", + "integrity": "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==", + "license": "MIT", + "dependencies": { + "encoding-japanese": "2.2.0", + "iconv-lite": "0.6.3", + "libbase64": "1.3.0", + "libqp": "2.1.1" + } + }, + "node_modules/libmime/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/libqp": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/libqp/-/libqp-2.1.1.tgz", + "integrity": "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==", + "license": "MIT" + }, "node_modules/lightningcss": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", @@ -4787,6 +5014,15 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4843,6 +5079,24 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/mailparser": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.9.1.tgz", + "integrity": "sha512-6vHZcco3fWsDMkf4Vz9iAfxvwrKNGbHx0dV1RKVphQ/zaNY34Buc7D37LSa09jeSeybWzYcTPjhiZFxzVRJedA==", + "license": "MIT", + "dependencies": { + "@zone-eu/mailsplit": "5.4.8", + "encoding-japanese": "2.2.0", + "he": "1.2.0", + "html-to-text": "9.0.5", + "iconv-lite": "0.7.0", + "libmime": "5.3.7", + "linkify-it": "5.0.0", + "nodemailer": "7.0.11", + "punycode.js": "2.3.1", + "tlds": "1.261.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5036,6 +5290,15 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemailer": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz", + "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5240,6 +5503,19 @@ "node": ">=6" } }, + "node_modules/parseley": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", + "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==", + "license": "MIT", + "dependencies": { + "leac": "^0.6.0", + "peberminta": "^0.9.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5267,6 +5543,15 @@ "dev": true, "license": "MIT" }, + "node_modules/peberminta": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz", + "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==", + "license": "MIT", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5357,6 +5642,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5450,6 +5744,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resend": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/resend/-/resend-6.9.1.tgz", + "integrity": "sha512-jFY3qPP2cith1npRXvS7PVdnhbR1CcuzHg65ty5Elv55GKiXhe+nItXuzzoOlKeYJez1iJAo2+8f6ae8sCj0iA==", + "license": "MIT", + "dependencies": { + "mailparser": "3.9.1", + "svix": "1.84.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@react-email/render": "*" + }, + "peerDependenciesMeta": { + "@react-email/render": { + "optional": true + } + } + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -5581,12 +5896,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, + "node_modules/selderee": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", + "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==", + "license": "MIT", + "dependencies": { + "parseley": "^0.12.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -5819,6 +6152,16 @@ "dev": true, "license": "MIT" }, + "node_modules/standardwebhooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -6018,6 +6361,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svix": { + "version": "1.84.1", + "resolved": "https://registry.npmjs.org/svix/-/svix-1.84.1.tgz", + "integrity": "sha512-K8DPPSZaW/XqXiz1kEyzSHYgmGLnhB43nQCMeKjWGCUpLIpAMMM8kx3rVVOSm6Bo6EHyK1RQLPT4R06skM/MlQ==", + "license": "MIT", + "dependencies": { + "standardwebhooks": "1.0.0", + "uuid": "^10.0.0" + } + }, "node_modules/tailwindcss": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", @@ -6087,6 +6440,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tlds": { + "version": "1.261.0", + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz", + "integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==", + "license": "MIT", + "bin": { + "tlds": "bin.js" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6274,6 +6636,12 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -6376,6 +6744,19 @@ "punycode": "^2.1.0" } }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 991bc2a..d4961b9 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "dependencies": { "next": "16.1.6", "react": "19.2.3", - "react-dom": "19.2.3" + "react-dom": "19.2.3", + "resend": "^6.9.1" }, "devDependencies": { "@tailwindcss/postcss": "^4",