mirror of
https://github.com/hanzoai/captable.git
synced 2026-10-16 14:21:29 +00:00
Send email through Hanzo notify
sendMail posts to /v1/notify/send/email as the app's own IAM identity (client credentials), and notify delivers with the email provider and sender configured for that org. SMTP settings and nodemailer are gone. notify carries no attachments, so the e-sign completion email links the signed document with a seven-day presigned URL instead of attaching it.
This commit is contained in:
@@ -35,10 +35,6 @@ IAM_CLIENT_SECRET=""
|
||||
# GOOGLE_CLIENT_ID=""
|
||||
# GOOGLE_CLIENT_SECRET=""
|
||||
|
||||
# SMTP
|
||||
EMAIL_FROM="'Captable, Inc.' <[email protected]>"
|
||||
EMAIL_SERVER=smtp://captable:password@localhost:1025
|
||||
|
||||
# Uploads
|
||||
UPLOAD_ENDPOINT="http://127.0.0.1:9002"
|
||||
NEXT_PUBLIC_UPLOAD_DOMAIN="http://127.0.0.1:9002"
|
||||
|
||||
@@ -186,7 +186,6 @@ When contributing to <strong>Hanzo Captable</strong>, whether on GitHub or in ot
|
||||
|
||||
- Install latest version of node and pnpm
|
||||
- Install latest version of postgres database
|
||||
- Install [mailpit](https://mailpit.axllent.org/docs/install/) for SMTP and email interception
|
||||
- Create database `captable` in postgres database
|
||||
- Update `.env` file's `DATABASE_URL` with database credentials
|
||||
- For a quick start, you can use [Supabase database](https://supabase.com/) or [Neon](https://neon.tech/) as well.
|
||||
|
||||
+8
-7
@@ -26,11 +26,11 @@ NEXTAUTH_SECRET="xxx" # Generated by `openssl rand -base64 32`
|
||||
NEXTAUTH_URL="https://your-domain.com"
|
||||
NEXT_PUBLIC_BASE_URL="https://your-domain.com"
|
||||
|
||||
# Email server environment variables
|
||||
EMAIL_FROM="[email protected]"
|
||||
|
||||
# Please use encrypted/TLS connection for SMTP server.
|
||||
EMAIL_SERVER="smtp://username:password@host:port"
|
||||
# Hanzo IAM application: sign-in, and the identity that sends email through
|
||||
# Hanzo notify with the org's email provider
|
||||
IAM_URL="https://hanzo.id"
|
||||
IAM_CLIENT_ID="xxx"
|
||||
IAM_CLIENT_SECRET="xxx"
|
||||
|
||||
# File uplod environment variables
|
||||
UPLOAD_REGION="us-west-1" # auto when using Cloudflare R2
|
||||
@@ -58,8 +58,9 @@ docker run -d \
|
||||
-e NEXTAUTH_SECRET="replace" \
|
||||
-e NEXTAUTH_URL="replace" \
|
||||
-e NEXT_PUBLIC_BASE_URL="replace" \
|
||||
-e EMAIL_FROM="replace" \
|
||||
-e EMAIL_SERVER="replace" \
|
||||
-e IAM_URL="replace" \
|
||||
-e IAM_CLIENT_ID="replace" \
|
||||
-e IAM_CLIENT_SECRET="replace" \
|
||||
-e UPLOAD_REGION="replace" \
|
||||
-e UPLOAD_ENDPOINT="replace" \
|
||||
-e NEXT_PUBLIC_UPLOAD_DOMAIN="replace" \
|
||||
|
||||
@@ -8,7 +8,6 @@ services:
|
||||
container_name: captable-app
|
||||
depends_on:
|
||||
- pg
|
||||
- smtp
|
||||
- minio
|
||||
restart: always
|
||||
ports:
|
||||
@@ -31,7 +30,6 @@ services:
|
||||
container_name: captable-studio
|
||||
depends_on:
|
||||
- pg
|
||||
- smtp
|
||||
- minio
|
||||
restart: always
|
||||
ports:
|
||||
@@ -59,13 +57,6 @@ services:
|
||||
volumes:
|
||||
- postgres:/var/lib/postgresql/data
|
||||
|
||||
smtp:
|
||||
image: axllent/mailpit
|
||||
container_name: captable-smtp
|
||||
ports:
|
||||
- 8025:8025
|
||||
- 1025:1025
|
||||
|
||||
minio:
|
||||
image: minio/minio
|
||||
container_name: captable-minio
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
"postinstall": "prisma generate",
|
||||
"prepare": "husky",
|
||||
"lint": "biome check --apply ./src",
|
||||
"email:dev": "mailpit",
|
||||
"format": "biome format --write ./src && prisma format",
|
||||
"email:preview": "email preview ./src/emails",
|
||||
"copy:pdfjs": "node scripts/copy-pdfjs-worker.cjs",
|
||||
@@ -93,7 +92,6 @@
|
||||
"next": "^15.1.0",
|
||||
"next-auth": "^4.24.7",
|
||||
"next-nprogress-bar": "^2.3.13",
|
||||
"nodemailer": "^6.9.14",
|
||||
"papaparse": "^5.4.1",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pg-boss": "^10.1.1",
|
||||
@@ -131,7 +129,6 @@
|
||||
"@types/inquirer": "^9.0.7",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/nodemailer": "^6.4.14",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.2.23",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
|
||||
Generated
+50
-25
@@ -194,9 +194,6 @@ importers:
|
||||
next-nprogress-bar:
|
||||
specifier: ^2.3.13
|
||||
version: 2.3.13
|
||||
nodemailer:
|
||||
specifier: ^6.9.14
|
||||
version: 6.9.14
|
||||
papaparse:
|
||||
specifier: ^5.4.1
|
||||
version: 5.4.1
|
||||
@@ -303,9 +300,6 @@ importers:
|
||||
'@types/node':
|
||||
specifier: ^20.12.12
|
||||
version: 20.12.12
|
||||
'@types/nodemailer':
|
||||
specifier: ^6.4.14
|
||||
version: 6.4.15
|
||||
'@types/react':
|
||||
specifier: ^18.3.3
|
||||
version: 18.3.3
|
||||
@@ -367,6 +361,7 @@ packages:
|
||||
|
||||
'@ark-ui/[email protected]':
|
||||
resolution: {integrity: sha512-pfHDkuFRFdzOnZxqNcAld1b03ehvFrOqXery9JHBTchP8+VmAmxg6ZDn4Se1ie4HvcmXD6IU61jltJB/aXYpJg==}
|
||||
deprecated: This package is deprecated. Please import directly from @ark-ui/<framework>
|
||||
|
||||
'@ark-ui/[email protected]':
|
||||
resolution: {integrity: sha512-W43rbimG1f7v1jk8J+PG/3um8/PaL1lQZEnRPzaXtQjEoiBlXH4yFyJakoEldxoZEQGB8x7SUkjHxZ93cRceuQ==}
|
||||
@@ -771,6 +766,7 @@ packages:
|
||||
'@ericcornelissen/[email protected]':
|
||||
resolution: {integrity: sha512-4pIMTa1nEFfMXitv7oaNEWOdM+zpOZavesa5GaiWTgda6Zk32CFGxjUp/iIaN0PwgUW1yTq/fztSjbpE8SLGZQ==}
|
||||
engines: {node: '>=4'}
|
||||
deprecated: Support for this package will stop 2025-12-31
|
||||
|
||||
'@esbuild/[email protected]':
|
||||
resolution: {integrity: sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==}
|
||||
@@ -2844,95 +2840,111 @@ packages:
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-dMJyL9aU25ieatdPtVjCyQ/WHZYHwNc+Hy/XpF8Cc18gu21cUynVEeYQzFSeigDRMeBQ3PGAyjVDPIob7YlGwA==}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-ioHdsk+BpGS/PqjU6JS7tUrVy9yvbUx92Z+Cem2+MbYp55oEwQ9VHf7u4f5NoM0gdhfKSehBwRdYlHt/frEMcg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-Zrhc71VYrSC1fVXJuaViKoB/dBjxLw6nbE53Bm/eUuZPdnnZ1+ZUIh8jfaRKC5MzMjgnLGQTweGXVnfIrhyxtQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-zj3oMQiiUCZbddSNt3k0zNfIBFK0ZNDIzzDyBaJKy6ZASTtWfB+1WFX0cpTX8q0gUiYK+A94rk5Qp68L6YXjXQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-Rsl7iSdDaeHZO938xb+0wR5ud0Z3MVfdtPbNKJNojZi2hApwLAQXmDrnn/AcPDM5Lpl331ZljJS8vHTWxxkvKw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-lnfVVrThEcET5NPoeaXvrz9UxtWpGRcut2a07dLbyKgNbP7vj/cXTI5TuHtanCvhCddFpMDnElNRghDOfPzwUg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-NgoaJJd9tTtsrveL86Ocr/AYLkGyN3prdXKd/zm5fQpfDhy/NXezyT3iF6VlwAOEUIu64ErHpAJd+P6ygR+vjg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-fSBEqYyVPAyyACBBHcs3wEYzNknpHMuwcSAAKE8fOoDfGqURr/vSxKPdh4tOa9z7G4hlcEfgGrCYEa2iPT22cw==}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-skw5FUgyamIMK+LN+fZQ5WIKQYf0dPiRAvsUAUR2eYoZp9oRsfkIpFHr0GWPkKAYjFEj+uJjaxQ/0VzQH7svVg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-jZM7IVuZOXa0G110ES8OkxajPTypIKlzlO1K1RIe1auk76ukQRiCg1IRV4HZlWk1GGUbec5hNxsvZa2kU8cb9w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-3AA4Yjgl3zEid/KVx6uf6TuLJHVZvUc2cG9Wm9ZpWeAX4ODA+8g9HyuC0tfnjbRsVMhMcCGiECuWWXINi+60vA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-06uiuSKJBWQJfhCKv4MPupELei4Lepyz9Sth7Yq7Fq29CAeB1ejLgKkGqn1I+FZ72hQxPLdYF4iq4yloKv3JCg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-pJ8glJjDNaJ53qoM95pvX9SK05yh0bNQY/oyBKmxlBDdUII6ixuMc3SCwYXPMl+tgkQUyDgwEBpSTrLAnjL3hA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-tva3wvAWSR10lMJa9fVA09yRn7pbEki0ZZpHE6GD1jKbFhmzt38VgLO9B797/prqoDZdAr4rVK7LJFcdPx3GwA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-wsuvj1XAb6O63aizCLNEeqVgKR3oFjAwt9vjfg2y2oh4G1dZeo8zonZM2x1fmkEkBZhzwSHraNi70jSXhA3A9w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-7O/CT4b16YlSGrj18htTPx3Vbhu2suCGv/cSe5c+fuSrIM/nMiBSZ3Js16Vj0XJbAmmmlVmYFZw9L20wXJ+LjQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
@@ -2946,24 +2958,28 @@ packages:
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-jPyEhG3gsLX+Eb9U+A30fh0gK6hXJwF4ghJ+ZtFQtlKAKqHX+eCpWlqB3Xschd/ARJLod8WAswg0FB+JD9d0/A==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-+fYWLb4tPU1A/+GE5J1+SEMA7/wR3V30lQ+OR9t2kAJqNrARDbMx0bLnYnR1QL5TiFRz0pCF05SQUobk6gHEDQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-qysVUEY+M3SKUvu35XDpzn7yokhqFOT3tPU6Mj/pgc62TL5tQFj6msEbBtwoKs2qO3WZvai0DIHdLhaOxBQSow==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@react-email/[email protected]':
|
||||
resolution: {integrity: sha512-wNAnxeEAiFs6N+SxS0y6wTJWfewEzUETuyS2aZmT00xk50VijwyFRuhm4sYSjusMyshevomFwz5jNISCxRsGWw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
@@ -3121,6 +3137,7 @@ packages:
|
||||
'@scalar/[email protected]':
|
||||
resolution: {integrity: sha512-7edLTztM7HKNOJ20rMd8YyzQUeoVuabSSlXuCxk6q+tzeSELMbAUlv3ZXZMINVZAWMIdvVsP3ktdpNAzWHNHHQ==}
|
||||
engines: {node: '>=18'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
|
||||
'@scalar/[email protected]':
|
||||
resolution: {integrity: sha512-GAVDclMEWerm6hlSU8dse+bzqxj4tzHCZLRVa6UtfCkdxmOqAaP46TDKasJAfuMPfWrVDNgGnIRwLBAz0u/zsQ==}
|
||||
@@ -3204,6 +3221,7 @@ packages:
|
||||
'@scalar/[email protected]':
|
||||
resolution: {integrity: sha512-OccT/4vWnz3MFoPRIRlFDFWFBca3GCxXQ9LjFaOTzb47W9Fb8l4mwsonZ/h8pCYNq0tZqaRul6G5J8AFzAgKrg==}
|
||||
engines: {node: '>=18'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
|
||||
'@selderee/[email protected]':
|
||||
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
|
||||
@@ -3343,6 +3361,7 @@ packages:
|
||||
|
||||
'@simplewebauthn/[email protected]':
|
||||
resolution: {integrity: sha512-SFXke7xkgPRowY2E+8djKbdEznTVnD5R6GO7GPTthpHrokLvNKw8C3lFZypTxLI7KkCfGPfhtqB3d7OVGGa9jQ==}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
|
||||
'@sinclair/[email protected]':
|
||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||
@@ -3549,6 +3568,7 @@ packages:
|
||||
'@snyk/[email protected]':
|
||||
resolution: {integrity: sha512-lGFf08pbkEac0NYgVf4hdANpAgApRjNByLXB+WBip3qj1iendOIyAwP2GKkKbQMNVy2r1xxDf0ssfWscoiC+Vw==}
|
||||
engines: {node: '>=8.10'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
hasBin: true
|
||||
|
||||
'@socket.io/[email protected]':
|
||||
@@ -3970,9 +3990,6 @@ packages:
|
||||
'@types/[email protected]':
|
||||
resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==}
|
||||
|
||||
'@types/[email protected]':
|
||||
resolution: {integrity: sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==}
|
||||
|
||||
'@types/[email protected]':
|
||||
resolution: {integrity: sha512-LxJ4iEFcpqc6METwp9f6BV6VVc43m6MfH0VqFosHvrUgfXiFe6ww7R3itkOQ+TCK6Y+Iv/+RnnvtRZnkc5Kc9g==}
|
||||
|
||||
@@ -4033,6 +4050,7 @@ packages:
|
||||
|
||||
'@ungap/[email protected]':
|
||||
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
||||
deprecated: Potential CWE-502 - Update to 1.3.1 or higher
|
||||
|
||||
'@unhead/[email protected]':
|
||||
resolution: {integrity: sha512-XZSZ2Wmm1Sv7k9scSFGrarbteSIl3p3I3oOUprKPDboBTvuG5q81Qz8O99NKUGKGJ8BKUkxCqE982eH3S8DKJA==}
|
||||
@@ -4375,6 +4393,7 @@ packages:
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
|
||||
deprecated: package has been renamed to acorn-import-attributes
|
||||
peerDependencies:
|
||||
acorn: ^8
|
||||
|
||||
@@ -4840,6 +4859,7 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
deprecated: v4 is no longer maintained, upgrade to v5
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==}
|
||||
@@ -5361,19 +5381,22 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting [email protected]
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting [email protected]
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting [email protected]
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting [email protected]
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||
@@ -6484,6 +6507,7 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
deprecated: Use your platform's native DOMException instead
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
@@ -7196,6 +7220,7 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==}
|
||||
engines: {node: '>=14'}
|
||||
deprecated: 1.x and 2.x branches are no longer active. Bump to Recharts v3 to receive latest features and bugfixes. See https://github.com/recharts/recharts/wiki/3.0-migration-guide
|
||||
peerDependencies:
|
||||
react: ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
@@ -7654,6 +7679,7 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
|
||||
engines: {node: '>=10'}
|
||||
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting [email protected]
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==}
|
||||
@@ -7952,10 +7978,12 @@ packages:
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
|
||||
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
@@ -8436,8 +8464,8 @@ snapshots:
|
||||
'@aws-crypto/sha1-browser': 3.0.0
|
||||
'@aws-crypto/sha256-browser': 3.0.0
|
||||
'@aws-crypto/sha256-js': 3.0.0
|
||||
'@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/[email protected])
|
||||
'@aws-sdk/client-sts': 3.577.0
|
||||
'@aws-sdk/client-sso-oidc': 3.577.0
|
||||
'@aws-sdk/client-sts': 3.577.0(@aws-sdk/[email protected])
|
||||
'@aws-sdk/core': 3.576.0
|
||||
'@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/[email protected])(@aws-sdk/[email protected])
|
||||
'@aws-sdk/middleware-bucket-endpoint': 3.577.0
|
||||
@@ -8494,11 +8522,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/[email protected](@aws-sdk/[email protected])':
|
||||
'@aws-sdk/[email protected]':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 3.0.0
|
||||
'@aws-crypto/sha256-js': 3.0.0
|
||||
'@aws-sdk/client-sts': 3.577.0
|
||||
'@aws-sdk/client-sts': 3.577.0(@aws-sdk/[email protected])
|
||||
'@aws-sdk/core': 3.576.0
|
||||
'@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/[email protected])(@aws-sdk/[email protected])
|
||||
'@aws-sdk/middleware-host-header': 3.577.0
|
||||
@@ -8537,7 +8565,6 @@ snapshots:
|
||||
'@smithy/util-utf8': 3.0.0
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/client-sts'
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/[email protected]':
|
||||
@@ -8583,11 +8610,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/[email protected]':
|
||||
'@aws-sdk/[email protected](@aws-sdk/[email protected])':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 3.0.0
|
||||
'@aws-crypto/sha256-js': 3.0.0
|
||||
'@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/[email protected])
|
||||
'@aws-sdk/client-sso-oidc': 3.577.0
|
||||
'@aws-sdk/core': 3.576.0
|
||||
'@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/[email protected])(@aws-sdk/[email protected])
|
||||
'@aws-sdk/middleware-host-header': 3.577.0
|
||||
@@ -8626,6 +8653,7 @@ snapshots:
|
||||
'@smithy/util-utf8': 3.0.0
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/client-sso-oidc'
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/[email protected]':
|
||||
@@ -8659,7 +8687,7 @@ snapshots:
|
||||
|
||||
'@aws-sdk/[email protected](@aws-sdk/[email protected])(@aws-sdk/[email protected])':
|
||||
dependencies:
|
||||
'@aws-sdk/client-sts': 3.577.0
|
||||
'@aws-sdk/client-sts': 3.577.0(@aws-sdk/[email protected])
|
||||
'@aws-sdk/credential-provider-env': 3.577.0
|
||||
'@aws-sdk/credential-provider-process': 3.577.0
|
||||
'@aws-sdk/credential-provider-sso': 3.577.0(@aws-sdk/[email protected])
|
||||
@@ -8716,7 +8744,7 @@ snapshots:
|
||||
|
||||
'@aws-sdk/[email protected](@aws-sdk/[email protected])':
|
||||
dependencies:
|
||||
'@aws-sdk/client-sts': 3.577.0
|
||||
'@aws-sdk/client-sts': 3.577.0(@aws-sdk/[email protected])
|
||||
'@aws-sdk/types': 3.577.0
|
||||
'@smithy/property-provider': 3.0.0
|
||||
'@smithy/types': 3.0.0
|
||||
@@ -8843,7 +8871,7 @@ snapshots:
|
||||
|
||||
'@aws-sdk/[email protected](@aws-sdk/[email protected])':
|
||||
dependencies:
|
||||
'@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/[email protected])
|
||||
'@aws-sdk/client-sso-oidc': 3.577.0
|
||||
'@aws-sdk/types': 3.577.0
|
||||
'@smithy/property-provider': 3.0.0
|
||||
'@smithy/shared-ini-file-loader': 3.0.0
|
||||
@@ -12771,10 +12799,6 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
'@types/[email protected]':
|
||||
dependencies:
|
||||
'@types/node': 20.12.12
|
||||
|
||||
'@types/[email protected]':
|
||||
dependencies:
|
||||
'@types/node': 20.12.12
|
||||
@@ -16124,7 +16148,8 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
[email protected]:
|
||||
optional: true
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Container,
|
||||
Head,
|
||||
Heading,
|
||||
Hr,
|
||||
Html,
|
||||
Preview,
|
||||
Section,
|
||||
Tailwind,
|
||||
Text,
|
||||
} from "@react-email/components";
|
||||
|
||||
import type { TSchema } from "../jobs/esign-confirmation-email";
|
||||
|
||||
type Payload = Omit<TSchema, "fileUrl">;
|
||||
|
||||
const ESignConfirmationEmail = ({
|
||||
documentName,
|
||||
recipient,
|
||||
senderName,
|
||||
company,
|
||||
}: Payload) => {
|
||||
fileUrl,
|
||||
}: TSchema) => {
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
@@ -40,10 +41,19 @@ const ESignConfirmationEmail = ({
|
||||
|
||||
<Text className="mt-5 text-[14px] leading-[24px] text-black">
|
||||
All parties have completed and signed the document -{" "}
|
||||
<strong>{documentName}</strong>. Please find the attached
|
||||
document.
|
||||
<strong>{documentName}</strong>. The download link below works
|
||||
for 7 days.
|
||||
</Text>
|
||||
|
||||
<Section className="mx-auto my-[32px] text-center">
|
||||
<Button
|
||||
className="rounded bg-black px-5 py-3 text-center text-[12px] font-semibold text-white no-underline"
|
||||
href={fileUrl}
|
||||
>
|
||||
Download the signed document
|
||||
</Button>
|
||||
</Section>
|
||||
|
||||
<Text className="mt-[20px] !text-[14px] leading-[24px] text-black">
|
||||
- {senderName}
|
||||
</Text>
|
||||
@@ -60,6 +70,7 @@ const ESignConfirmationEmail = ({
|
||||
};
|
||||
|
||||
ESignConfirmationEmail.PreviewProps = {
|
||||
fileUrl: "https://example.com/document.pdf",
|
||||
documentName: "Document Name",
|
||||
recipient: { name: "Recipient Name", email: "" },
|
||||
senderName: "Sender Name",
|
||||
|
||||
@@ -38,8 +38,6 @@ export const env = createEnv({
|
||||
.transform((s) => s !== "false" && s !== "0"),
|
||||
NEXTAUTH_URL: z.string(),
|
||||
NEXTAUTH_SECRET: z.string(),
|
||||
EMAIL_SERVER: z.string().optional(),
|
||||
EMAIL_FROM: z.string(),
|
||||
|
||||
// upload
|
||||
UPLOAD_ENDPOINT: z.string(),
|
||||
@@ -89,8 +87,6 @@ export const env = createEnv({
|
||||
DATABASE_URL: process.env.DATABASE_URL,
|
||||
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
|
||||
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
|
||||
EMAIL_SERVER: process.env.EMAIL_SERVER,
|
||||
EMAIL_FROM: process.env.EMAIL_FROM,
|
||||
|
||||
UPLOAD_ENDPOINT: process.env.UPLOAD_ENDPOINT,
|
||||
UPLOAD_REGION: process.env.UPLOAD_REGION,
|
||||
|
||||
@@ -66,7 +66,8 @@ export const eSignCompletePDFWorker = defineWorker(config, async (job) => {
|
||||
});
|
||||
});
|
||||
|
||||
const file = await getPresignedGetUrl(bucketData.key);
|
||||
// The emailed download link: seven days is the longest a SigV4 URL lives.
|
||||
const file = await getPresignedGetUrl(bucketData.key, 7 * 24 * 60 * 60);
|
||||
|
||||
await eSignConfirmationEmailJob.bulkEmit(
|
||||
recipients.map((recipient) => ({
|
||||
|
||||
@@ -39,23 +39,13 @@ export const eSignConfirmationEmailWorker = defineWorker(
|
||||
senderName: payload.senderName,
|
||||
senderEmail: payload.senderEmail,
|
||||
company: payload.company,
|
||||
fileUrl: payload.fileUrl,
|
||||
}),
|
||||
);
|
||||
await sendMail({
|
||||
to: payload.recipient.email,
|
||||
...(payload.senderEmail && { replyTo: payload.senderEmail }),
|
||||
subject: "Completed e-signed documents from all parties",
|
||||
html,
|
||||
attachments: [
|
||||
{
|
||||
filename: payload.documentName,
|
||||
path: payload.fileUrl,
|
||||
},
|
||||
],
|
||||
|
||||
headers: {
|
||||
"X-From-Name": payload.senderName || "Captable",
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -98,12 +98,8 @@ export const eSignNotificationEmailWorker = defineWorker(
|
||||
);
|
||||
await sendMail({
|
||||
to: email,
|
||||
...(sender?.email && { replyTo: sender.email }),
|
||||
subject: "eSign Document Request",
|
||||
html,
|
||||
headers: {
|
||||
"X-From-Name": sender?.name || "Captable",
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -27,11 +27,9 @@ export const shareDataRoomEmailWorker = defineWorker(config, async (job) => {
|
||||
recipientName,
|
||||
senderName,
|
||||
email,
|
||||
senderEmail,
|
||||
} = job.data;
|
||||
await sendMail({
|
||||
to: email,
|
||||
...(senderEmail && { replyTo: senderEmail }),
|
||||
subject: `${senderName} shared a data room - ${dataRoom}`,
|
||||
html: await render(
|
||||
ShareDataRoomEmail({
|
||||
@@ -42,9 +40,5 @@ export const shareDataRoomEmailWorker = defineWorker(config, async (job) => {
|
||||
link,
|
||||
}),
|
||||
),
|
||||
|
||||
headers: {
|
||||
"X-From-Name": senderName,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,11 +29,9 @@ export const shareUpdateEmailWorker = defineWorker(config, async (job) => {
|
||||
recipientName,
|
||||
senderName,
|
||||
email,
|
||||
senderEmail,
|
||||
} = job.data;
|
||||
await sendMail({
|
||||
to: email,
|
||||
...(senderEmail && { replyTo: senderEmail }),
|
||||
subject: `${senderName} shared an update - ${update.title}`,
|
||||
html: await render(
|
||||
ShareUpdateEmail({
|
||||
@@ -44,9 +42,5 @@ export const shareUpdateEmailWorker = defineWorker(config, async (job) => {
|
||||
link,
|
||||
}),
|
||||
),
|
||||
|
||||
headers: {
|
||||
"X-From-Name": senderName,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -89,7 +89,10 @@ export const getPresignedPutUrl = async ({
|
||||
return { url, key: Key, bucketUrl: bucketUrl.toString() };
|
||||
};
|
||||
|
||||
export const getPresignedGetUrl = async (key: string) => {
|
||||
export const getPresignedGetUrl = async (
|
||||
key: string,
|
||||
expiresIn = TEN_MINUTES_IN_SECONDS,
|
||||
) => {
|
||||
const getObjectCommand = new GetObjectCommand({
|
||||
Bucket: PrivateBucket,
|
||||
Key: key,
|
||||
@@ -97,9 +100,7 @@ export const getPresignedGetUrl = async (key: string) => {
|
||||
ResponseContentDisposition: "inline",
|
||||
});
|
||||
|
||||
const url = await getSignedUrl(S3, getObjectCommand, {
|
||||
expiresIn: TEN_MINUTES_IN_SECONDS,
|
||||
});
|
||||
const url = await getSignedUrl(S3, getObjectCommand, { expiresIn });
|
||||
|
||||
return { key, url };
|
||||
};
|
||||
|
||||
+54
-27
@@ -1,32 +1,59 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { captableApiBase } from "@/server/captable-api";
|
||||
|
||||
import { env } from "@/env";
|
||||
import { type SendMailOptions, createTransport } from "nodemailer";
|
||||
type Mail = { to: string; subject: string; html: string };
|
||||
|
||||
const getTransport = () => {
|
||||
return createTransport({
|
||||
url: env.EMAIL_SERVER,
|
||||
const IAM_URL = process.env.IAM_URL || "https://hanzo.id";
|
||||
|
||||
/** This app's own IAM identity, minted with the client-credentials grant. */
|
||||
async function appToken(): Promise<string> {
|
||||
const basic = Buffer.from(
|
||||
`${process.env.IAM_CLIENT_ID}:${process.env.IAM_CLIENT_SECRET}`,
|
||||
).toString("base64");
|
||||
const res = await fetch(`${IAM_URL}/v1/iam/oauth/token`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
authorization: `Basic ${basic}`,
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: new URLSearchParams({ grant_type: "client_credentials" }),
|
||||
cache: "no-store",
|
||||
});
|
||||
};
|
||||
|
||||
type RecordType = Record<string, string | undefined>;
|
||||
|
||||
export const sendMail = (options: Omit<SendMailOptions, "from">) => {
|
||||
let from = `Captable <${env.EMAIL_FROM}>`;
|
||||
const headers = (options.headers || {}) as RecordType;
|
||||
|
||||
const senderName = headers["X-From-Name"];
|
||||
|
||||
if (senderName) {
|
||||
from = `${senderName} <${env.EMAIL_FROM}>`;
|
||||
const body = (await res.json().catch(() => ({}))) as {
|
||||
access_token?: string;
|
||||
error?: string;
|
||||
};
|
||||
if (!body.access_token) {
|
||||
throw new Error(`IAM token refused (${res.status}): ${body.error ?? ""}`);
|
||||
}
|
||||
return body.access_token;
|
||||
}
|
||||
|
||||
const transport = getTransport();
|
||||
return transport.sendMail({
|
||||
from,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Delivers one email through Hanzo notify (POST /v1/notify/send/email). notify
|
||||
* sends with the email provider and sender address configured for this app's
|
||||
* org, so there is no SMTP server or from-address here.
|
||||
*/
|
||||
export async function sendMail({ to, subject, html }: Mail): Promise<void> {
|
||||
const res = await fetch(
|
||||
`${captableApiBase()}/v1/notify/send/email?sync=true`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
authorization: `Bearer ${await appToken()}`,
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ to: [to], subject, body: html, sync: "true" }),
|
||||
cache: "no-store",
|
||||
},
|
||||
);
|
||||
const out = (await res.json().catch(() => ({}))) as {
|
||||
status?: string;
|
||||
error?: string;
|
||||
detail?: string;
|
||||
};
|
||||
if (!res.ok || out.status === "failed") {
|
||||
throw new Error(
|
||||
`notify did not send "${subject}" (${res.status}): ${out.error ?? out.detail ?? ""}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user