mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-06 11:20:56 -05:00
fix: email smtp auth mode (#4571)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
This commit is contained in:
@@ -46,6 +46,9 @@ SMTP_SECURE_ENABLED=0
|
||||
SMTP_USER=smtpUser
|
||||
SMTP_PASSWORD=smtpPassword
|
||||
|
||||
# If set to 0, the server will not require SMTP_USER and SMTP_PASSWORD(default is 1)
|
||||
# SMTP_AUTHENTICATED=
|
||||
|
||||
# If set to 0, the server will accept connections without requiring authorization from the list of supplied CAs (default is 1).
|
||||
# SMTP_REJECT_UNAUTHORIZED_TLS=0
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ These variables are present inside your machine’s docker-compose file. Restart
|
||||
| SMTP_PORT | Host Port of your SMTP server. | optional (required if email services are to be enabled) | |
|
||||
| SMTP_USER | Username for your SMTP Server. | optional (required if email services are to be enabled) | |
|
||||
| SMTP_PASSWORD | Password for your SMTP Server. | optional (required if email services are to be enabled) | |
|
||||
| SMTP_AUTHENTICATED | If set to 0, the server will not require SMTP_USER and SMTP_PASSWORD(default is 1) | optional | |
|
||||
| SMTP_SECURE_ENABLED | SMTP secure connection. For using TLS, set to 1 else to 0. | optional (required if email services are to be enabled) | |
|
||||
| SMTP_REJECT_UNAUTHORIZED_TLS | If set to 0, the server will accept connections without requiring authorization from the list of supplied CAs. | optional | 1 |
|
||||
| TURNSTILE_SITE_KEY | Site key for Turnstile. | optional | |
|
||||
|
||||
+8
-2
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { CodeBlock } from "@/modules/ui/components/code-block";
|
||||
import { LoadingSpinner } from "@/modules/ui/components/loading-spinner";
|
||||
@@ -38,8 +39,13 @@ export const EmailTab = ({ surveyId, email }: EmailTabProps) => {
|
||||
|
||||
const sendPreviewEmail = async () => {
|
||||
try {
|
||||
await sendEmbedSurveyPreviewEmailAction({ surveyId });
|
||||
toast.success(t("environments.surveys.summary.email_sent"));
|
||||
const val = await sendEmbedSurveyPreviewEmailAction({ surveyId });
|
||||
if (val?.data) {
|
||||
toast.success(t("environments.surveys.summary.email_sent"));
|
||||
} else {
|
||||
const errorMessage = getFormattedErrorMessage(val);
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof AuthenticationError) {
|
||||
toast.error(t("common.not_authenticated"));
|
||||
|
||||
+6
-2
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { inviteOrganizationMemberAction } from "@/app/setup/organization/[organizationId]/invite/actions";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/modules/ui/components/alert";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { FormControl, FormError, FormField, FormItem, FormProvider } from "@/modules/ui/components/form";
|
||||
@@ -34,13 +35,16 @@ export const InviteMembers = ({ IS_SMTP_CONFIGURED, organizationId }: InviteMemb
|
||||
for (const member of Object.values(data)) {
|
||||
try {
|
||||
if (!member.email) continue;
|
||||
await inviteOrganizationMemberAction({
|
||||
const inviteResponse = await inviteOrganizationMemberAction({
|
||||
email: member.email.toLowerCase(),
|
||||
name: member.name,
|
||||
organizationId,
|
||||
});
|
||||
if (IS_SMTP_CONFIGURED) {
|
||||
if (inviteResponse?.data) {
|
||||
toast.success(`${t("setup.invite.invitation_sent_to")} ${member.email}!`);
|
||||
} else {
|
||||
const errorMessage = getFormattedErrorMessage(inviteResponse);
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(`${t("setup.invite.failed_to_invite")} ${member.email}.`);
|
||||
|
||||
@@ -20,6 +20,5 @@ export const resendVerificationEmailAction = actionClient
|
||||
if (user.emailVerified) {
|
||||
throw new InvalidInputError("Email address has already been verified");
|
||||
}
|
||||
await sendVerificationEmail(user);
|
||||
return { success: true };
|
||||
return await sendVerificationEmail(user);
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import type SMTPTransport from "nodemailer/lib/smtp-transport";
|
||||
import {
|
||||
DEBUG,
|
||||
MAIL_FROM,
|
||||
SMTP_AUTHENTICATED,
|
||||
SMTP_HOST,
|
||||
SMTP_PASSWORD,
|
||||
SMTP_PORT,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
import { createInviteToken, createToken, createTokenForLinkSurvey } from "@formbricks/lib/jwt";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import type { TLinkSurveyEmailData } from "@formbricks/types/email";
|
||||
import { InvalidInputError } from "@formbricks/types/errors";
|
||||
import type { TResponse } from "@formbricks/types/responses";
|
||||
import type { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { TUserEmail, TUserLocale } from "@formbricks/types/user";
|
||||
@@ -48,27 +50,37 @@ const getEmailSubject = (projectName: string): string => {
|
||||
return `${projectName} User Insights - Last Week by Formbricks`;
|
||||
};
|
||||
|
||||
export const sendEmail = async (emailData: SendEmailDataProps): Promise<void> => {
|
||||
if (!IS_SMTP_CONFIGURED) return;
|
||||
export const sendEmail = async (emailData: SendEmailDataProps): Promise<boolean> => {
|
||||
try {
|
||||
const transporter = createTransport({
|
||||
host: SMTP_HOST,
|
||||
port: SMTP_PORT,
|
||||
secure: SMTP_SECURE_ENABLED, // true for 465, false for other ports
|
||||
...(SMTP_AUTHENTICATED
|
||||
? {
|
||||
auth: {
|
||||
type: "LOGIN",
|
||||
user: SMTP_USER,
|
||||
pass: SMTP_PASSWORD,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
tls: {
|
||||
rejectUnauthorized: SMTP_REJECT_UNAUTHORIZED_TLS,
|
||||
},
|
||||
logger: DEBUG,
|
||||
debug: DEBUG,
|
||||
} as SMTPTransport.Options);
|
||||
|
||||
const transporter = createTransport({
|
||||
host: SMTP_HOST,
|
||||
port: SMTP_PORT,
|
||||
secure: SMTP_SECURE_ENABLED, // true for 465, false for other ports
|
||||
auth: {
|
||||
user: SMTP_USER,
|
||||
pass: SMTP_PASSWORD,
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: SMTP_REJECT_UNAUTHORIZED_TLS,
|
||||
},
|
||||
logger: DEBUG,
|
||||
debug: DEBUG,
|
||||
} as SMTPTransport.Options);
|
||||
const emailDefaults = {
|
||||
from: `Formbricks <${MAIL_FROM ?? "noreply@formbricks.com"}>`,
|
||||
};
|
||||
await transporter.sendMail({ ...emailDefaults, ...emailData });
|
||||
const emailDefaults = {
|
||||
from: `Formbricks <${MAIL_FROM ?? "noreply@formbricks.com"}>`,
|
||||
};
|
||||
await transporter.sendMail({ ...emailDefaults, ...emailData });
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw new InvalidInputError("Incorrect SMTP credentials");
|
||||
}
|
||||
};
|
||||
|
||||
export const sendVerificationEmail = async ({
|
||||
@@ -79,14 +91,14 @@ export const sendVerificationEmail = async ({
|
||||
id: string;
|
||||
email: TUserEmail;
|
||||
locale: TUserLocale;
|
||||
}): Promise<void> => {
|
||||
}): Promise<boolean> => {
|
||||
const token = createToken(id, email, {
|
||||
expiresIn: "1d",
|
||||
});
|
||||
const verifyLink = `${WEBAPP_URL}/auth/verify?token=${encodeURIComponent(token)}`;
|
||||
const verificationRequestLink = `${WEBAPP_URL}/auth/verification-requested?token=${encodeURIComponent(token)}`;
|
||||
const html = await render(VerificationEmail({ verificationRequestLink, verifyLink, locale }));
|
||||
await sendEmail({
|
||||
return await sendEmail({
|
||||
to: email,
|
||||
subject: translateEmailText("verification_email_subject", locale),
|
||||
html,
|
||||
@@ -97,13 +109,13 @@ export const sendForgotPasswordEmail = async (user: {
|
||||
id: string;
|
||||
email: TUserEmail;
|
||||
locale: TUserLocale;
|
||||
}): Promise<void> => {
|
||||
}): Promise<boolean> => {
|
||||
const token = createToken(user.id, user.email, {
|
||||
expiresIn: "1d",
|
||||
});
|
||||
const verifyLink = `${WEBAPP_URL}/auth/forgot-password/reset?token=${encodeURIComponent(token)}`;
|
||||
const html = await render(ForgotPasswordEmail({ verifyLink, locale: user.locale }));
|
||||
await sendEmail({
|
||||
return await sendEmail({
|
||||
to: user.email,
|
||||
subject: "Reset your Formbricks password",
|
||||
html,
|
||||
@@ -113,9 +125,9 @@ export const sendForgotPasswordEmail = async (user: {
|
||||
export const sendPasswordResetNotifyEmail = async (user: {
|
||||
email: string;
|
||||
locale: TUserLocale;
|
||||
}): Promise<void> => {
|
||||
}): Promise<boolean> => {
|
||||
const html = await render(PasswordResetNotifyEmail({ locale: user.locale }));
|
||||
await sendEmail({
|
||||
return await sendEmail({
|
||||
to: user.email,
|
||||
subject: "Your Formbricks password has been changed",
|
||||
html,
|
||||
@@ -130,7 +142,7 @@ export const sendInviteMemberEmail = async (
|
||||
isOnboardingInvite?: boolean,
|
||||
inviteMessage?: string,
|
||||
locale = "en-US"
|
||||
): Promise<void> => {
|
||||
): Promise<boolean> => {
|
||||
const token = createInviteToken(inviteId, email, {
|
||||
expiresIn: "7d",
|
||||
});
|
||||
@@ -141,14 +153,14 @@ export const sendInviteMemberEmail = async (
|
||||
const html = await render(
|
||||
OnboardingInviteEmail({ verifyLink, inviteMessage, inviterName, locale, inviteeName })
|
||||
);
|
||||
await sendEmail({
|
||||
return await sendEmail({
|
||||
to: email,
|
||||
subject: `${inviterName} needs a hand setting up Formbricks. Can you help out?`,
|
||||
html,
|
||||
});
|
||||
} else {
|
||||
const html = await render(InviteEmail({ inviteeName, inviterName, verifyLink, locale }));
|
||||
await sendEmail({
|
||||
return await sendEmail({
|
||||
to: email,
|
||||
subject: `You're invited to collaborate on Formbricks!`,
|
||||
html,
|
||||
@@ -214,9 +226,9 @@ export const sendEmbedSurveyPreviewEmail = async (
|
||||
environmentId: string,
|
||||
locale: string,
|
||||
logoUrl?: string
|
||||
): Promise<void> => {
|
||||
): Promise<boolean> => {
|
||||
const html = await render(EmbedSurveyPreviewEmail({ html: innerHtml, environmentId, locale, logoUrl }));
|
||||
await sendEmail({
|
||||
return await sendEmail({
|
||||
to,
|
||||
subject,
|
||||
html,
|
||||
@@ -229,17 +241,17 @@ export const sendEmailCustomizationPreviewEmail = async (
|
||||
userName: string,
|
||||
locale: string,
|
||||
logoUrl?: string
|
||||
): Promise<void> => {
|
||||
): Promise<boolean> => {
|
||||
const emailHtmlBody = await render(EmailCustomizationPreviewEmail({ userName, locale, logoUrl }));
|
||||
|
||||
await sendEmail({
|
||||
return await sendEmail({
|
||||
to,
|
||||
subject,
|
||||
html: emailHtmlBody,
|
||||
});
|
||||
};
|
||||
|
||||
export const sendLinkSurveyToVerifiedEmail = async (data: TLinkSurveyEmailData): Promise<void> => {
|
||||
export const sendLinkSurveyToVerifiedEmail = async (data: TLinkSurveyEmailData): Promise<boolean> => {
|
||||
const surveyId = data.surveyId;
|
||||
const email = data.email;
|
||||
const surveyName = data.surveyName;
|
||||
@@ -256,7 +268,7 @@ export const sendLinkSurveyToVerifiedEmail = async (data: TLinkSurveyEmailData):
|
||||
const surveyLink = getSurveyLink();
|
||||
|
||||
const html = await render(LinkSurveyEmail({ surveyName, surveyLink, locale, logoUrl }));
|
||||
await sendEmail({
|
||||
return await sendEmail({
|
||||
to: data.email,
|
||||
subject: "Your survey is ready to be filled out.",
|
||||
html,
|
||||
|
||||
+10
-2
@@ -94,8 +94,16 @@ export const MemberActions = ({ organization, member, invite, showDeleteButton }
|
||||
try {
|
||||
if (!invite) return;
|
||||
|
||||
await resendInviteAction({ inviteId: invite.id, organizationId: organization.id });
|
||||
toast.success(t("environments.settings.general.invitation_sent_once_more"));
|
||||
const resendInviteResponse = await resendInviteAction({
|
||||
inviteId: invite.id,
|
||||
organizationId: organization.id,
|
||||
});
|
||||
if (resendInviteResponse?.data) {
|
||||
toast.success(t("environments.settings.general.invitation_sent_once_more"));
|
||||
} else {
|
||||
const errorMessage = getFormattedErrorMessage(resendInviteResponse);
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error(`${t("common.error")}: ${err.message}`);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ x-environment: &environment
|
||||
# SMTP_PORT:
|
||||
# SMTP_USER:
|
||||
# SMTP_PASSWORD:
|
||||
# SMTP_AUTHENTICATED:
|
||||
|
||||
# (Additional option for TLS (port 465) only)
|
||||
# SMTP_SECURE_ENABLED: 1
|
||||
|
||||
@@ -235,6 +235,9 @@ EOT
|
||||
|
||||
echo -n "Enter your SMTP password: "
|
||||
read smtp_password
|
||||
|
||||
echo -n "Enable Authenticated SMTP? Enter 1 for yes and 0 for no(default is 1): "
|
||||
read smtp_authenticated
|
||||
|
||||
echo -n "Enable Secure SMTP (use SSL)? Enter 1 for yes and 0 for no: "
|
||||
read smtp_secure_enabled
|
||||
@@ -245,6 +248,7 @@ EOT
|
||||
smtp_port=""
|
||||
smtp_user=""
|
||||
smtp_password=""
|
||||
smtp_authenticated=1
|
||||
smtp_secure_enabled=0
|
||||
fi
|
||||
|
||||
@@ -271,6 +275,7 @@ EOT
|
||||
sed -i "s|# SMTP_SECURE_ENABLED:|SMTP_SECURE_ENABLED: $smtp_secure_enabled|" docker-compose.yml
|
||||
sed -i "s|# SMTP_USER:|SMTP_USER: \"$smtp_user\"|" docker-compose.yml
|
||||
sed -i "s|# SMTP_PASSWORD:|SMTP_PASSWORD: \"$smtp_password\"|" docker-compose.yml
|
||||
sed -i "s|# SMTP_AUTHENTICATED:|SMTP_AUTHENTICATED: $smtp_authenticated|" docker-compose.yml
|
||||
fi
|
||||
|
||||
awk -v domain_name="$domain_name" -v hsts_enabled="$hsts_enabled" '
|
||||
|
||||
+30
-15
@@ -19,20 +19,34 @@ Harvest user-insights, build irresistible experiences.
|
||||
|
||||
# Formbricks Helm Chart: Comprehensive Documentation
|
||||
|
||||
1. [Introduction](#introduction)
|
||||
2. [Prerequisites](#prerequisites)
|
||||
3. [Chart Components](#chart-components)
|
||||
4. [Installation](#installation)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Usage Examples](#usage-examples)
|
||||
5. [Configuration](#configuration)
|
||||
6. [Environment Variables](#environment-variables)
|
||||
7. [Scaling](#scaling)
|
||||
8. [Upgrading Formbricks](#upgrading-formbricks)
|
||||
9. [Support](#support)
|
||||
10. [Full Values Documentation](#full-values-documentation)
|
||||
11. [Contribution](#contribution)
|
||||
12. [MicroK8s Installation and Formbricks Deployment](#microk8s-installation-and-formbricks-deployment)
|
||||
- [Formbricks Helm Chart: Comprehensive Documentation](#formbricks-helm-chart-comprehensive-documentation)
|
||||
- [Introduction](#introduction)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Chart Components](#chart-components)
|
||||
- [Installation](#installation)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Usage Examples](#usage-examples)
|
||||
- [Scaling PostgreSQL and Redis](#scaling-postgresql-and-redis)
|
||||
- [Configuration](#configuration)
|
||||
- [Environment Variables](#environment-variables)
|
||||
- [Scaling](#scaling)
|
||||
- [With Auto Scaling (Kubernetes Metrics Server Requirement)](#with-auto-scaling-kubernetes-metrics-server-requirement)
|
||||
- [Customizing Autoscaling](#customizing-autoscaling)
|
||||
- [Kubernetes Metrics Server Requirement](#kubernetes-metrics-server-requirement)
|
||||
- [Advanced Autoscaling Configuration](#advanced-autoscaling-configuration)
|
||||
- [Upgrading Formbricks](#upgrading-formbricks)
|
||||
- [Upgrade Process](#upgrade-process)
|
||||
- [Common Upgrade Scenarios](#common-upgrade-scenarios)
|
||||
- [1. Updating Environment Variables](#1-updating-environment-variables)
|
||||
- [2. Enabling or Disabling Features](#2-enabling-or-disabling-features)
|
||||
- [3. Scaling Resources](#3-scaling-resources)
|
||||
- [4. Updating Autoscaling Configuration](#4-updating-autoscaling-configuration)
|
||||
- [5. Changing Database Credentials](#5-changing-database-credentials)
|
||||
- [Using a Values File for Complex Upgrades](#using-a-values-file-for-complex-upgrades)
|
||||
- [Support](#support)
|
||||
- [Full Values Documentation](#full-values-documentation)
|
||||
- [✍️ Contribution](#️-contribution)
|
||||
- [MicroK8s Installation and Formbricks Deployment](#microk8s-installation-and-formbricks-deployment)
|
||||
- [MicroK8s Quick Setup](#microk8s-quick-setup)
|
||||
- [Deploying Formbricks on MicroK8s](#deploying-formbricks-on-microk8s)
|
||||
|
||||
@@ -206,7 +220,8 @@ These documents provide detailed information on scaling and configuring high ava
|
||||
--set env.SMTP_HOST=smtp.example.com \
|
||||
--set env.SMTP_PORT=587 \
|
||||
--set env.SMTP_USER=user@example.com \
|
||||
--set env.SMTP_PASSWORD=password123
|
||||
--set env.SMTP_PASSWORD=password123 \
|
||||
--set env.SMTP_AUTHENTICATED=1
|
||||
```
|
||||
|
||||
5. **Installation with Custom Resource Limits**:
|
||||
|
||||
@@ -72,6 +72,7 @@ export const SMTP_PORT = env.SMTP_PORT;
|
||||
export const SMTP_SECURE_ENABLED = env.SMTP_SECURE_ENABLED === "1";
|
||||
export const SMTP_USER = env.SMTP_USER;
|
||||
export const SMTP_PASSWORD = env.SMTP_PASSWORD;
|
||||
export const SMTP_AUTHENTICATED = env.SMTP_AUTHENTICATED !== "0";
|
||||
export const SMTP_REJECT_UNAUTHORIZED_TLS = env.SMTP_REJECT_UNAUTHORIZED_TLS !== "0";
|
||||
export const MAIL_FROM = env.MAIL_FROM;
|
||||
|
||||
|
||||
+3
-1
@@ -78,10 +78,11 @@ export const env = createEnv({
|
||||
SLACK_CLIENT_ID: z.string().optional(),
|
||||
SLACK_CLIENT_SECRET: z.string().optional(),
|
||||
SMTP_HOST: z.string().min(1).optional(),
|
||||
SMTP_PASSWORD: z.string().min(1).optional(),
|
||||
SMTP_PORT: z.string().min(1).optional(),
|
||||
SMTP_SECURE_ENABLED: z.enum(["1", "0"]).optional(),
|
||||
SMTP_USER: z.string().min(1).optional(),
|
||||
SMTP_PASSWORD: z.string().min(1).optional(),
|
||||
SMTP_AUTHENTICATED: z.enum(["1", "0"]).optional(),
|
||||
SMTP_REJECT_UNAUTHORIZED_TLS: z.enum(["1", "0"]).optional(),
|
||||
STRIPE_SECRET_KEY: z.string().optional(),
|
||||
STRIPE_WEBHOOK_SECRET: z.string().optional(),
|
||||
@@ -206,6 +207,7 @@ export const env = createEnv({
|
||||
SMTP_SECURE_ENABLED: process.env.SMTP_SECURE_ENABLED,
|
||||
SMTP_USER: process.env.SMTP_USER,
|
||||
SMTP_REJECT_UNAUTHORIZED_TLS: process.env.SMTP_REJECT_UNAUTHORIZED_TLS,
|
||||
SMTP_AUTHENTICATED: process.env.SMTP_AUTHENTICATED,
|
||||
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
|
||||
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET,
|
||||
TELEMETRY_DISABLED: process.env.TELEMETRY_DISABLED,
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"SMTP_SECURE_ENABLED",
|
||||
"SMTP_USER",
|
||||
"SMTP_REJECT_UNAUTHORIZED_TLS",
|
||||
"SMTP_AUTHENTICATED",
|
||||
"STRAPI_API_KEY",
|
||||
"STRIPE_SECRET_KEY",
|
||||
"STRIPE_WEBHOOK_SECRET",
|
||||
|
||||
Reference in New Issue
Block a user