mirror of
https://github.com/documenso/documenso.git
synced 2025-11-16 01:32:06 +10:00
feat: add trpc route to send emails
This commit is contained in:
@ -17,3 +17,10 @@ NEXT_PUBLIC_SUBSCRIPTIONS_ENABLED=false
|
|||||||
# This is only required for the marketing site
|
# This is only required for the marketing site
|
||||||
NEXT_PRIVATE_REDIS_URL=
|
NEXT_PRIVATE_REDIS_URL=
|
||||||
NEXT_PRIVATE_REDIS_TOKEN=
|
NEXT_PRIVATE_REDIS_TOKEN=
|
||||||
|
|
||||||
|
# Mailserver
|
||||||
|
NEXT_PRIVATE_SENDGRID_API_KEY=
|
||||||
|
NEXT_PRIVATE_SMTP_MAIL_HOST=
|
||||||
|
NEXT_PRIVATE_SMTP_MAIL_PORT=
|
||||||
|
NEXT_PRIVATE_SMTP_MAIL_USER=
|
||||||
|
NEXT_PRIVATE_SMTP_MAIL_PASSWORD=
|
||||||
@ -43,9 +43,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<PlausibleProvider>
|
<PlausibleProvider>{children}</PlausibleProvider>
|
||||||
{children}
|
|
||||||
</PlausibleProvider>
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -25,6 +25,8 @@
|
|||||||
"next-auth": "^4.22.1",
|
"next-auth": "^4.22.1",
|
||||||
"next-plausible": "^3.7.2",
|
"next-plausible": "^3.7.2",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
|
"nodemailer": "^6.9.3",
|
||||||
|
"nodemailer-sendgrid": "^1.0.3",
|
||||||
"perfect-freehand": "^1.2.0",
|
"perfect-freehand": "^1.2.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
@ -38,6 +40,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/formidable": "^2.0.6",
|
"@types/formidable": "^2.0.6",
|
||||||
"@types/node": "20.1.0",
|
"@types/node": "20.1.0",
|
||||||
|
"@types/nodemailer": "^6.4.8",
|
||||||
|
"@types/nodemailer-sendgrid": "^1.0.0",
|
||||||
"@types/react": "18.2.6",
|
"@types/react": "18.2.6",
|
||||||
"@types/react-dom": "18.2.4"
|
"@types/react-dom": "18.2.4"
|
||||||
}
|
}
|
||||||
|
|||||||
6
apps/web/process-env.d.ts
vendored
6
apps/web/process-env.d.ts
vendored
@ -11,5 +11,11 @@ declare namespace NodeJS {
|
|||||||
NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET: string;
|
NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET: string;
|
||||||
|
|
||||||
NEXT_PUBLIC_SUBSCRIPTIONS_ENABLED: string;
|
NEXT_PUBLIC_SUBSCRIPTIONS_ENABLED: string;
|
||||||
|
|
||||||
|
NEXT_PRIVATE_SENDGRID_API_KEY: string;
|
||||||
|
NEXT_PRIVATE_SMTP_MAIL_HOST: string;
|
||||||
|
NEXT_PRIVATE_SMTP_MAIL_PORT: string;
|
||||||
|
NEXT_PRIVATE_SMTP_MAIL_USER: string;
|
||||||
|
NEXT_PRIVATE_SMTP_MAIL_PASSWORD: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
apps/web/src/app/send/page.tsx
Normal file
24
apps/web/src/app/send/page.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { trpc } from '@documenso/trpc/react';
|
||||||
|
|
||||||
|
export default function Send() {
|
||||||
|
const { mutateAsync: sendMail } = trpc.document.sendEmail.useMutation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-20">
|
||||||
|
<button
|
||||||
|
className="rounded-md border-2 border-solid border-black px-4 py-2 text-2xl"
|
||||||
|
onClick={async () => {
|
||||||
|
console.log('clicked');
|
||||||
|
|
||||||
|
await sendMail({ email: 'duncan@documenso.com' });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
972
package-lock.json
generated
972
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
packages/lib/server-only/document/send-document.ts
Normal file
39
packages/lib/server-only/document/send-document.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
import nodemailerSendgrid from 'nodemailer-sendgrid';
|
||||||
|
|
||||||
|
export const sendMail = async ({ email }: { email: string }) => {
|
||||||
|
let transporter;
|
||||||
|
|
||||||
|
if (process.env.NEXT_PRIVATE_SENDGRID_API_KEY) {
|
||||||
|
transporter = nodemailer.createTransport(
|
||||||
|
nodemailerSendgrid({
|
||||||
|
apiKey: process.env.NEXT_PRIVATE_SENDGRID_API_KEY,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.NEXT_PRIVATE_SMTP_MAIL_HOST) {
|
||||||
|
transporter = nodemailer.createTransport({
|
||||||
|
host: process.env.NEXT_PRIVATE_SMTP_MAIL_HOST,
|
||||||
|
port: Number(process.env.NEXT_PRIVATE_SMTP_MAIL_PORT),
|
||||||
|
auth: {
|
||||||
|
user: process.env.NEXT_PRIVATE_SMTP_MAIL_USER,
|
||||||
|
pass: process.env.NEXT_PRIVATE_SMTP_MAIL_PASSWORD,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transporter) {
|
||||||
|
throw new Error(
|
||||||
|
'No mail transport configured. Probably Sendgrid API Key nor SMTP Mail host was set',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await transporter.sendMail({
|
||||||
|
from: 'Documenso <hi@documenso.com>',
|
||||||
|
to: email,
|
||||||
|
subject: 'Welcome to Documenso!',
|
||||||
|
text: 'Welcome to Documenso!',
|
||||||
|
html: '<p>Welcome to Documenso!</p>',
|
||||||
|
});
|
||||||
|
};
|
||||||
26
packages/trpc/server/document-router/router.ts
Normal file
26
packages/trpc/server/document-router/router.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { TRPCError } from '@trpc/server';
|
||||||
|
|
||||||
|
import { sendMail } from '@documenso/lib/server-only/document/send-document';
|
||||||
|
|
||||||
|
import { authenticatedProcedure, router } from '../trpc';
|
||||||
|
import { ZSendMailMutationSchema } from './schema';
|
||||||
|
|
||||||
|
export const documentRouter = router({
|
||||||
|
sendEmail: authenticatedProcedure
|
||||||
|
.input(ZSendMailMutationSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
try {
|
||||||
|
const { email } = input;
|
||||||
|
|
||||||
|
console.log('Send Mail Context', ctx);
|
||||||
|
return await sendMail({ email });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'BAD_REQUEST',
|
||||||
|
message: 'We were unable to send an email.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
||||||
7
packages/trpc/server/document-router/schema.ts
Normal file
7
packages/trpc/server/document-router/schema.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const ZSendMailMutationSchema = z.object({
|
||||||
|
email: z.string().min(1).email(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSendMailMutationSchema = z.infer<typeof ZSendMailMutationSchema>;
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { authRouter } from './auth-router/router';
|
import { authRouter } from './auth-router/router';
|
||||||
|
import { documentRouter } from './document-router/router';
|
||||||
import { profileRouter } from './profile-router/router';
|
import { profileRouter } from './profile-router/router';
|
||||||
import { procedure, router } from './trpc';
|
import { procedure, router } from './trpc';
|
||||||
|
|
||||||
@ -6,6 +7,7 @@ export const appRouter = router({
|
|||||||
hello: procedure.query(() => 'Hello, world!'),
|
hello: procedure.query(() => 'Hello, world!'),
|
||||||
auth: authRouter,
|
auth: authRouter,
|
||||||
profile: profileRouter,
|
profile: profileRouter,
|
||||||
|
document: documentRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppRouter = typeof appRouter;
|
export type AppRouter = typeof appRouter;
|
||||||
|
|||||||
@ -18,6 +18,12 @@
|
|||||||
"NEXT_PUBLIC_SITE_URL",
|
"NEXT_PUBLIC_SITE_URL",
|
||||||
"NEXT_PRIVATE_DATABASE_URL",
|
"NEXT_PRIVATE_DATABASE_URL",
|
||||||
"NEXT_PRIVATE_NEXT_AUTH_SECRET",
|
"NEXT_PRIVATE_NEXT_AUTH_SECRET",
|
||||||
"NEXT_PUBLIC_SUBSCRIPTIONS_ENABLED"
|
"NEXT_PUBLIC_SUBSCRIPTIONS_ENABLED",
|
||||||
|
|
||||||
|
"NEXT_PRIVATE_SENDGRID_API_KEY",
|
||||||
|
"NEXT_PRIVATE_SMTP_MAIL_HOST",
|
||||||
|
"NEXT_PRIVATE_SMTP_MAIL_PORT",
|
||||||
|
"NEXT_PRIVATE_SMTP_MAIL_USER",
|
||||||
|
"NEXT_PRIVATE_SMTP_MAIL_PASSWORD"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user