From e32078f9a659dd73f40c124e608b09eddaca8871 Mon Sep 17 00:00:00 2001 From: Ilya Kreymer Date: Mon, 13 Nov 2023 18:58:43 -0800 Subject: [PATCH 1/4] email templates work --- backend/btrixcloud/emailsender.py | 94 +++++++++++---------------- backend/btrixcloud/invites.py | 9 +-- backend/btrixcloud/models.py | 2 +- chart/email-templates/failed_bg_job | 16 +++++ chart/email-templates/invite | 27 ++++++++ chart/email-templates/invite_existing | 7 ++ chart/email-templates/password_reset | 4 ++ chart/email-templates/validate | 7 ++ chart/templates/backend.yaml | 10 +++ chart/templates/configmap.yaml | 12 ++++ chart/values.yaml | 1 + 11 files changed, 127 insertions(+), 62 deletions(-) create mode 100644 chart/email-templates/failed_bg_job create mode 100644 chart/email-templates/invite create mode 100644 chart/email-templates/invite_existing create mode 100644 chart/email-templates/password_reset create mode 100644 chart/email-templates/validate diff --git a/backend/btrixcloud/emailsender.py b/backend/btrixcloud/emailsender.py index 3b7c6c6d90..662a5c31c9 100644 --- a/backend/btrixcloud/emailsender.py +++ b/backend/btrixcloud/emailsender.py @@ -7,12 +7,13 @@ from typing import Optional, Union from email.message import EmailMessage +from fastapi.templating import Jinja2Templates from .models import CreateReplicaJob, DeleteReplicaJob, Organization from .utils import is_bool -# pylint: disable=too-few-public-methods +# pylint: disable=too-few-public-methods, too-many-instance-attributes class EmailSender: """SMTP Email Sender""" @@ -27,14 +28,29 @@ def __init__(self): self.sender = os.environ.get("EMAIL_SENDER") or "Browsertrix admin" self.password = os.environ.get("EMAIL_PASSWORD") or "" self.reply_to = os.environ.get("EMAIL_REPLY_TO") or self.sender + self.support_email = os.environ.get("EMAIL_SUPPORT_EMAIL") or self.reply_to self.smtp_server = os.environ.get("EMAIL_SMTP_HOST") self.smtp_port = int(os.environ.get("EMAIL_SMTP_PORT", 587)) self.smtp_use_tls = is_bool(os.environ.get("EMAIL_SMTP_USE_TLS")) self.default_origin = os.environ.get("APP_ORIGIN") - def _send_encrypted(self, receiver, subject, message) -> None: - """Send Encrypted SMTP Message""" + self.templates = Jinja2Templates( + directory=os.path.join(os.path.dirname(__file__), "email-templates") + ) + + def _send_encrypted(self, receiver: str, name: str, **kwargs) -> None: + """Send Encrypted SMTP Message using given template name""" + + full = self.templates.env.get_template(name).render(kwargs) + if full.startswith("Subject:"): + subject, message = full.split("\n", 1) + subject = subject.split(":", 1)[1].strip() + message = message.strip("\n") + else: + subject = "" + message = full + print(message, flush=True) if not self.smtp_server: @@ -76,18 +92,7 @@ def send_user_validation(self, receiver_email, token, headers=None): origin = self.get_origin(headers) - message = f""" -Please verify your registration for Browsertrix Cloud for {receiver_email} - -You can verify by clicking here: {origin}/verify?token={token} - -The verification token is: {token}""" - - self._send_encrypted( - receiver_email, - "Welcome to Browsertrix Cloud, Verify your Registration", - message, - ) + self._send_encrypted(receiver_email, "validate", origin=origin, token=token) # pylint: disable=too-many-arguments def send_new_user_invite( @@ -97,17 +102,14 @@ def send_new_user_invite( origin = self.get_origin(headers) - message = f""" -You are invited by {sender} to join their organization, "{org_name}" on Browsertrix Cloud! - -You can join by clicking here: {origin}/join/{token}?email={receiver_email} - -The invite token is: {token}""" - self._send_encrypted( receiver_email, - f'You\'ve been invited to join "{org_name}" on Browsertrix Cloud', - message, + "invite", + origin=origin, + token=token, + sender=sender, + org_name=org_name, + support_email=self.support_email ) # pylint: disable=too-many-arguments @@ -117,29 +119,22 @@ def send_existing_user_invite( """Send email to invite new user""" origin = self.get_origin(headers) - message = f""" -You are invited by {sender} to join their organization, "{org_name}" on Browsertrix Cloud! - -You can join by clicking here: {origin}/invite/accept/{token}?email={receiver_email} - -The invite token is: {token}""" - self._send_encrypted( receiver_email, - f'You\'ve been invited to join "{org_name}" on Browsertrix Cloud', - message, + "invite_existing", + origin=origin, + token=token, + sender=sender, + org_name=org_name, ) def send_user_forgot_password(self, receiver_email, token, headers=None): """Send password reset email with token""" origin = self.get_origin(headers) - message = f""" -We received your password reset request. Please click here: {origin}/reset-password?token={token} -to create a new password - """ - - self._send_encrypted(receiver_email, "Password Reset", message) + self._send_encrypted( + receiver_email, "password_reset", origin=origin, token=token + ) def send_background_job_failed( self, @@ -149,21 +144,6 @@ def send_background_job_failed( receiver_email: str, ): """Send background job failed email to superuser""" - message = f""" -Failed Background Job ---------------------- - -Organization: {org.name} ({job.oid}) -Job type: {job.type} - -Job ID: {job.id} -Started: {job.started.isoformat(sep=" ", timespec="seconds")}Z -Finished: {finished.isoformat(sep=" ", timespec="seconds")}Z - -Object type: {job.object_type} -Object ID: {job.object_id} -File path: {job.file_path} -Replica storage name: {job.replica_storage.name} - """ - - self._send_encrypted(receiver_email, "Failed Background Job", message) + self._send_encrypted( + receiver_email, "failed_bg_job", job=job, org=org, finished=finished + ) diff --git a/backend/btrixcloud/invites.py b/backend/btrixcloud/invites.py index a7885ee2f1..2d322f79c4 100644 --- a/backend/btrixcloud/invites.py +++ b/backend/btrixcloud/invites.py @@ -11,7 +11,8 @@ from fastapi import HTTPException from .pagination import DEFAULT_PAGE_SIZE -from .models import UserRole, InvitePending, InviteRequest +from .models import UserRole, InvitePending, InviteRequest, User +from .users import UserManager from .utils import is_bool @@ -118,8 +119,8 @@ async def accept_user_invite(self, user, invite_token: str, user_manager): async def invite_user( self, invite: InviteRequest, - user, - user_manager, + user: User, + user_manager: UserManager, org=None, allow_existing=False, headers: Optional[dict] = None, @@ -147,7 +148,7 @@ async def invite_user( role=invite.role if hasattr(invite, "role") else None, # URL decode email address just in case email=urllib.parse.unquote(invite.email), - inviterEmail=user.email, + inviterEmail=user.email if not user.is_superuser else None, ) other_user = await user_manager.get_by_email(invite.email) diff --git a/backend/btrixcloud/models.py b/backend/btrixcloud/models.py index f973beed9f..8f17b60d82 100644 --- a/backend/btrixcloud/models.py +++ b/backend/btrixcloud/models.py @@ -47,7 +47,7 @@ class InvitePending(BaseMongoModel): """An invite for a new user, with an email and invite token as id""" created: datetime - inviterEmail: str + inviterEmail: Optional[str] oid: Optional[UUID] role: Optional[UserRole] = UserRole.VIEWER email: Optional[str] diff --git a/chart/email-templates/failed_bg_job b/chart/email-templates/failed_bg_job new file mode 100644 index 0000000000..2101901adb --- /dev/null +++ b/chart/email-templates/failed_bg_job @@ -0,0 +1,16 @@ +Subject: Failed Background Job + +Failed Background Job +--------------------- + +Organization: {{ org.name }} ({{ job.oid }}) +Job type: {{ job.type }} + +Job ID: {{ job.id }} +Started: {{ job.started.isoformat(sep=" ", timespec="seconds") }}Z +Finished: {{ finished.isoformat(sep=" ", timespec="seconds") }}Z + +Object type: {{ job.object_type }} +Object ID: {{ job.object_id }} +File path: {{ job.file_path }} +Replica storage name: {{ job.replica_storage.name }} diff --git a/chart/email-templates/invite b/chart/email-templates/invite new file mode 100644 index 0000000000..81d6fe5f28 --- /dev/null +++ b/chart/email-templates/invite @@ -0,0 +1,27 @@ +Subject: Welcome to Browsertrix Cloud! + +Hello! + +Welcome to Browsertrix Cloud! + +{% if sender %} +You have been invited by {{ sender }} to join their organization, "{{ org_name }}" on Browsertrix Cloud! + +You can join by clicking here: {{ origin }}/invite/accept/{{ token }}?email={{ receiver_email }} +{% else %} + +You can join by clicking here: {{ origin }}/join/{{ token }}?email={{ receiver_email }} +{% endif %} + +When you first access your account, you’ll be directed to your Dashboard. It contains information you may want to view frequently including: Storage Usage, Crawling Info, Collections, and Monthly Usage History. + +For more info, check out the Browsertrix Cloud User Guide + + +If you ever need to reset your password, go here: Reset my password please! + + +We want you to get the most from your Browsertrix Cloud experience. Let us know if you need any questions or feedback. +You can connect with our team at {{ email }}. + +Webrecorder Team diff --git a/chart/email-templates/invite_existing b/chart/email-templates/invite_existing new file mode 100644 index 0000000000..0cc684460b --- /dev/null +++ b/chart/email-templates/invite_existing @@ -0,0 +1,7 @@ +Subject: You've been invited to join "{{ org_name }}" on Browsertrix Cloud + +You are invited by {{ sender }} to join their organization, "{{ org_name }}" on Browsertrix Cloud! + +You can join by clicking here: {{ origin }}/invite/accept/{{ token }}?email={receiver_email} + +The invite token is: {{ token }} diff --git a/chart/email-templates/password_reset b/chart/email-templates/password_reset new file mode 100644 index 0000000000..840d717870 --- /dev/null +++ b/chart/email-templates/password_reset @@ -0,0 +1,4 @@ +Subject: Password Reset + +We received your password reset request. Please click here: {{ origin }}/reset-password?token={{ token }} +to create a new password. diff --git a/chart/email-templates/validate b/chart/email-templates/validate new file mode 100644 index 0000000000..a0cb7bc2e9 --- /dev/null +++ b/chart/email-templates/validate @@ -0,0 +1,7 @@ +Subject: Welcome to Browsertrix Cloud, Verify your Registration. + +Please verify your registration for Browsertrix Cloud for {{ receiver_email }} + +You can verify by clicking here: {{ origin }}/verify?token={{ token }} + +The verification token is: {{ token }} diff --git a/chart/templates/backend.yaml b/chart/templates/backend.yaml index 4a9c762475..c271c0e654 100644 --- a/chart/templates/backend.yaml +++ b/chart/templates/backend.yaml @@ -45,6 +45,10 @@ spec: configMap: name: app-templates + - name: email-templates + configMap: + name: email-templates + containers: - name: api image: {{ .Values.backend_image }} @@ -68,6 +72,9 @@ spec: - name: app-templates mountPath: /app/btrixcloud/templates/ + - name: email-templates + mountPath: /app/btrixcloud/email-templates/ + resources: limits: memory: {{ .Values.backend_memory }} @@ -139,6 +146,9 @@ spec: - name: app-templates mountPath: /app/btrixcloud/templates/ + - name: email-templates + mountPath: /app/btrixcloud/email-templates/ + resources: limits: memory: {{ .Values.backend_memory }} diff --git a/chart/templates/configmap.yaml b/chart/templates/configmap.yaml index 023631d3b1..17cd4c1574 100644 --- a/chart/templates/configmap.yaml +++ b/chart/templates/configmap.yaml @@ -48,6 +48,8 @@ data: STORAGES_JSON: "/ops-configs/storages.json" + SUPPORT_EMAIL: "{{ .Values.email.support_email }}" + --- apiVersion: v1 kind: ConfigMap @@ -135,3 +137,13 @@ metadata: data: {{ (.Files.Glob "app-templates/*.yaml").AsConfig | indent 2 }} + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: email-templates + namespace: {{ .Release.Namespace }} + +data: +{{ (.Files.Glob "email-templates/*").AsConfig | indent 2 }} diff --git a/chart/values.yaml b/chart/values.yaml index daf34cf4fd..9b8bb42021 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -293,6 +293,7 @@ email: password: password reply_to_email: example@example.com use_tls: True + support_email: support@example.com # Deployment options From 381e29e2c714fa11153310e2bff37626bfe377dd Mon Sep 17 00:00:00 2001 From: Ilya Kreymer Date: Mon, 13 Nov 2023 19:02:11 -0800 Subject: [PATCH 2/4] relint --- backend/btrixcloud/emailsender.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/btrixcloud/emailsender.py b/backend/btrixcloud/emailsender.py index 662a5c31c9..3454be6055 100644 --- a/backend/btrixcloud/emailsender.py +++ b/backend/btrixcloud/emailsender.py @@ -23,6 +23,8 @@ class EmailSender: smtp_server: Optional[str] smtp_port: int smtp_use_tls: bool + support_email: str + templates: Jinja2Templates def __init__(self): self.sender = os.environ.get("EMAIL_SENDER") or "Browsertrix admin" @@ -109,7 +111,7 @@ def send_new_user_invite( token=token, sender=sender, org_name=org_name, - support_email=self.support_email + support_email=self.support_email, ) # pylint: disable=too-many-arguments From 8ddd6ff1632a6a13887eaac04830ecd991b3ab3d Mon Sep 17 00:00:00 2001 From: Ilya Kreymer Date: Tue, 14 Nov 2023 00:14:37 -0800 Subject: [PATCH 3/4] support HTML templates --- backend/btrixcloud/emailsender.py | 44 +++++++++++++++++--------- backend/btrixcloud/invites.py | 3 +- chart/email-templates/failed_bg_job | 4 +-- chart/email-templates/invite | 45 ++++++++++++++++++++++----- chart/email-templates/invite_existing | 4 +-- chart/email-templates/password_reset | 4 +-- chart/email-templates/validate | 4 +-- 7 files changed, 78 insertions(+), 30 deletions(-) diff --git a/backend/btrixcloud/emailsender.py b/backend/btrixcloud/emailsender.py index 3454be6055..c9f59a42db 100644 --- a/backend/btrixcloud/emailsender.py +++ b/backend/btrixcloud/emailsender.py @@ -7,6 +7,9 @@ from typing import Optional, Union from email.message import EmailMessage +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from fastapi import HTTPException from fastapi.templating import Jinja2Templates from .models import CreateReplicaJob, DeleteReplicaJob, Organization @@ -45,26 +48,35 @@ def _send_encrypted(self, receiver: str, name: str, **kwargs) -> None: """Send Encrypted SMTP Message using given template name""" full = self.templates.env.get_template(name).render(kwargs) - if full.startswith("Subject:"): - subject, message = full.split("\n", 1) - subject = subject.split(":", 1)[1].strip() - message = message.strip("\n") + parts = full.split("~~~") + if len(parts) == 3: + subject, html, text = parts + elif len(parts) == 2: + subject, text = parts + html = None else: - subject = "" - message = full + raise HTTPException(status_code=500, detail="invalid_email_template") - print(message, flush=True) + print(full, flush=True) if not self.smtp_server: print("Email: No SMTP Server, not sending", flush=True) return - msg = EmailMessage() + msg: Union[EmailMessage, MIMEMultipart] + + if html: + msg = MIMEMultipart("alternative") + msg.attach(MIMEText(text.strip(), "plain")) + msg.attach(MIMEText(html.strip(), "html")) + else: + msg = EmailMessage() + msg.set_content(text.strip()) + msg["Subject"] = subject msg["From"] = self.reply_to msg["To"] = receiver msg["Reply-To"] = msg["From"] - msg.set_content(message) context = ssl.create_default_context() with smtplib.SMTP(self.smtp_server, self.smtp_port) as server: @@ -104,11 +116,13 @@ def send_new_user_invite( origin = self.get_origin(headers) + invite_url = f"{origin}/join/{token}?email={receiver_email}" + self._send_encrypted( receiver_email, "invite", - origin=origin, - token=token, + invite_url=invite_url, + is_new=True, sender=sender, org_name=org_name, support_email=self.support_email, @@ -121,11 +135,13 @@ def send_existing_user_invite( """Send email to invite new user""" origin = self.get_origin(headers) + invite_url = f"{origin}/invite/accept/{token}?email={receiver_email}" + self._send_encrypted( receiver_email, - "invite_existing", - origin=origin, - token=token, + "invite", + invite_url=invite_url, + is_new=False, sender=sender, org_name=org_name, ) diff --git a/backend/btrixcloud/invites.py b/backend/btrixcloud/invites.py index 2d322f79c4..b1804dfdf5 100644 --- a/backend/btrixcloud/invites.py +++ b/backend/btrixcloud/invites.py @@ -148,7 +148,8 @@ async def invite_user( role=invite.role if hasattr(invite, "role") else None, # URL decode email address just in case email=urllib.parse.unquote(invite.email), - inviterEmail=user.email if not user.is_superuser else None, + # inviterEmail=user.email if not user.is_superuser else None, + inviterEmail=user.email, ) other_user = await user_manager.get_by_email(invite.email) diff --git a/chart/email-templates/failed_bg_job b/chart/email-templates/failed_bg_job index 2101901adb..7d825467d3 100644 --- a/chart/email-templates/failed_bg_job +++ b/chart/email-templates/failed_bg_job @@ -1,5 +1,5 @@ -Subject: Failed Background Job - +Failed Background Job +~~~ Failed Background Job --------------------- diff --git a/chart/email-templates/invite b/chart/email-templates/invite index 81d6fe5f28..b40d3ecb56 100644 --- a/chart/email-templates/invite +++ b/chart/email-templates/invite @@ -1,5 +1,36 @@ -Subject: Welcome to Browsertrix Cloud! +Welcome to Browsertrix Cloud! +~~~ + + +

Hello!

+ +

Welcome to Browsertrix Cloud!

+ +{% if sender %} +

You have been invited by {{ sender }} to join "{{ org_name }}" on Browsertrix Cloud! +

+{% endif %} + +{% if is_new %} +

Click here to sign up for an account!

+{% else %} +

Click here to accept this invite.

+{% endif %} +

When you first access your account, you’ll be directed to your Dashboard. It contains information you may want to view frequently including: Storage Usage, Crawling Info, Collections, and Monthly Usage History.

+ + +

For more info, check out the Browsertrix Cloud User Guide

+ + +

+We want you to get the most from your Browsertrix Cloud experience. Let us know if you need any questions or feedback. +You can connect with our team at {{ support_email }}

+ +

The Webrecorder Team

+ + +~~~ Hello! Welcome to Browsertrix Cloud! @@ -7,21 +38,21 @@ Welcome to Browsertrix Cloud! {% if sender %} You have been invited by {{ sender }} to join their organization, "{{ org_name }}" on Browsertrix Cloud! -You can join by clicking here: {{ origin }}/invite/accept/{{ token }}?email={{ receiver_email }} {% else %} -You can join by clicking here: {{ origin }}/join/{{ token }}?email={{ receiver_email }} +You can join by clicking here: {{ invite_url }} {% endif %} When you first access your account, you’ll be directed to your Dashboard. It contains information you may want to view frequently including: Storage Usage, Crawling Info, Collections, and Monthly Usage History. -For more info, check out the Browsertrix Cloud User Guide +For more info, check out Browsertrix Cloud User Guide at: https://docs.browsertrix.cloud/user-guide/ -If you ever need to reset your password, go here: Reset my password please! +If you ever need to reset your password, go here: {{ origin }}/log-in/forgot-password We want you to get the most from your Browsertrix Cloud experience. Let us know if you need any questions or feedback. -You can connect with our team at {{ email }}. +You can connect with our team at {{ support_email }}. + + -Webrecorder Team diff --git a/chart/email-templates/invite_existing b/chart/email-templates/invite_existing index 0cc684460b..705e72d21c 100644 --- a/chart/email-templates/invite_existing +++ b/chart/email-templates/invite_existing @@ -1,5 +1,5 @@ -Subject: You've been invited to join "{{ org_name }}" on Browsertrix Cloud - +You've been invited to join "{{ org_name }}" on Browsertrix Cloud +~~~ You are invited by {{ sender }} to join their organization, "{{ org_name }}" on Browsertrix Cloud! You can join by clicking here: {{ origin }}/invite/accept/{{ token }}?email={receiver_email} diff --git a/chart/email-templates/password_reset b/chart/email-templates/password_reset index 840d717870..ec00389f28 100644 --- a/chart/email-templates/password_reset +++ b/chart/email-templates/password_reset @@ -1,4 +1,4 @@ -Subject: Password Reset - +Password Reset +~~~ We received your password reset request. Please click here: {{ origin }}/reset-password?token={{ token }} to create a new password. diff --git a/chart/email-templates/validate b/chart/email-templates/validate index a0cb7bc2e9..51bc0fed5b 100644 --- a/chart/email-templates/validate +++ b/chart/email-templates/validate @@ -1,5 +1,5 @@ -Subject: Welcome to Browsertrix Cloud, Verify your Registration. - +Welcome to Browsertrix Cloud, Verify your Registration. +~~~ Please verify your registration for Browsertrix Cloud for {{ receiver_email }} You can verify by clicking here: {{ origin }}/verify?token={{ token }} From 649ce3ab744a6b1df1ce2d3bdffa30513966aa12 Mon Sep 17 00:00:00 2001 From: Ilya Kreymer Date: Tue, 14 Nov 2023 10:32:52 -0800 Subject: [PATCH 4/4] various fixes, don't include 'invited by' if the inviter is the superuser tewak text on invite and password reset, fix text-only emails move support email to secrets --- backend/btrixcloud/emailsender.py | 22 +++++++++++++--------- backend/btrixcloud/invites.py | 10 ++-------- backend/btrixcloud/models.py | 3 ++- chart/email-templates/invite | 12 +++++++++--- chart/email-templates/invite_existing | 7 ------- chart/email-templates/password_reset | 10 ++++++++-- chart/templates/configmap.yaml | 2 -- chart/templates/secrets.yaml | 1 + 8 files changed, 35 insertions(+), 32 deletions(-) delete mode 100644 chart/email-templates/invite_existing diff --git a/backend/btrixcloud/emailsender.py b/backend/btrixcloud/emailsender.py index c9f59a42db..e097c32f29 100644 --- a/backend/btrixcloud/emailsender.py +++ b/backend/btrixcloud/emailsender.py @@ -12,7 +12,7 @@ from fastapi import HTTPException from fastapi.templating import Jinja2Templates -from .models import CreateReplicaJob, DeleteReplicaJob, Organization +from .models import CreateReplicaJob, DeleteReplicaJob, Organization, InvitePending from .utils import is_bool @@ -33,7 +33,7 @@ def __init__(self): self.sender = os.environ.get("EMAIL_SENDER") or "Browsertrix admin" self.password = os.environ.get("EMAIL_PASSWORD") or "" self.reply_to = os.environ.get("EMAIL_REPLY_TO") or self.sender - self.support_email = os.environ.get("EMAIL_SUPPORT_EMAIL") or self.reply_to + self.support_email = os.environ.get("EMAIL_SUPPORT") or self.reply_to self.smtp_server = os.environ.get("EMAIL_SMTP_HOST") self.smtp_port = int(os.environ.get("EMAIL_SMTP_PORT", 587)) self.smtp_use_tls = is_bool(os.environ.get("EMAIL_SMTP_USE_TLS")) @@ -73,7 +73,7 @@ def _send_encrypted(self, receiver: str, name: str, **kwargs) -> None: msg = EmailMessage() msg.set_content(text.strip()) - msg["Subject"] = subject + msg["Subject"] = subject.strip() msg["From"] = self.reply_to msg["To"] = receiver msg["Reply-To"] = msg["From"] @@ -109,21 +109,21 @@ def send_user_validation(self, receiver_email, token, headers=None): self._send_encrypted(receiver_email, "validate", origin=origin, token=token) # pylint: disable=too-many-arguments - def send_new_user_invite( - self, receiver_email, sender, org_name, token, headers=None - ): + def send_new_user_invite(self, invite: InvitePending, org_name: str, headers=None): """Send email to invite new user""" origin = self.get_origin(headers) - invite_url = f"{origin}/join/{token}?email={receiver_email}" + receiver_email = invite.email or "" + + invite_url = f"{origin}/join/{invite.id}?email={receiver_email}" self._send_encrypted( receiver_email, "invite", invite_url=invite_url, is_new=True, - sender=sender, + sender=invite.inviterEmail if not invite.fromSuperuser else "", org_name=org_name, support_email=self.support_email, ) @@ -151,7 +151,11 @@ def send_user_forgot_password(self, receiver_email, token, headers=None): origin = self.get_origin(headers) self._send_encrypted( - receiver_email, "password_reset", origin=origin, token=token + receiver_email, + "password_reset", + origin=origin, + token=token, + support_email=self.support_email, ) def send_background_job_failed( diff --git a/backend/btrixcloud/invites.py b/backend/btrixcloud/invites.py index b1804dfdf5..3b204f9768 100644 --- a/backend/btrixcloud/invites.py +++ b/backend/btrixcloud/invites.py @@ -71,13 +71,7 @@ async def add_new_user_invite( await self.invites.insert_one(new_user_invite.to_dict()) - self.email.send_new_user_invite( - new_user_invite.email, - new_user_invite.inviterEmail, - org_name, - new_user_invite.id, - headers, - ) + self.email.send_new_user_invite(new_user_invite, org_name, headers) async def get_valid_invite(self, invite_token: UUID, email): """Retrieve a valid invite data from db, or throw if invalid""" @@ -148,8 +142,8 @@ async def invite_user( role=invite.role if hasattr(invite, "role") else None, # URL decode email address just in case email=urllib.parse.unquote(invite.email), - # inviterEmail=user.email if not user.is_superuser else None, inviterEmail=user.email, + fromSuperuser=user.is_superuser, ) other_user = await user_manager.get_by_email(invite.email) diff --git a/backend/btrixcloud/models.py b/backend/btrixcloud/models.py index 8f17b60d82..2d03990c28 100644 --- a/backend/btrixcloud/models.py +++ b/backend/btrixcloud/models.py @@ -47,7 +47,8 @@ class InvitePending(BaseMongoModel): """An invite for a new user, with an email and invite token as id""" created: datetime - inviterEmail: Optional[str] + inviterEmail: str + fromSuperuser: Optional[bool] oid: Optional[UUID] role: Optional[UserRole] = UserRole.VIEWER email: Optional[str] diff --git a/chart/email-templates/invite b/chart/email-templates/invite index b40d3ecb56..61a2469d3b 100644 --- a/chart/email-templates/invite +++ b/chart/email-templates/invite @@ -12,20 +12,26 @@ Welcome to Browsertrix Cloud! {% endif %} {% if is_new %} -

Click here to sign up for an account!

+

You can now set up your account using the link below.

+ +

Click here to create an account.

{% else %}

Click here to accept this invite.

{% endif %} -

When you first access your account, you’ll be directed to your Dashboard. It contains information you may want to view frequently including: Storage Usage, Crawling Info, Collections, and Monthly Usage History.

+

When you first access your account, you’ll be directed to your Dashboard. It contains information you may want to view frequently including: Storage Usage, Crawling Info, Collections, and Monthly Usage History. From there, you can click + Create New to create your first Crawl Workflow!

For more info, check out the Browsertrix Cloud User Guide

-We want you to get the most from your Browsertrix Cloud experience. Let us know if you need any questions or feedback. +We want you to get the most from your Browsertrix Cloud experience! +

+ +

Let us know if you need any questions or feedback.

You can connect with our team at {{ support_email }}

+

The Webrecorder Team

diff --git a/chart/email-templates/invite_existing b/chart/email-templates/invite_existing deleted file mode 100644 index 705e72d21c..0000000000 --- a/chart/email-templates/invite_existing +++ /dev/null @@ -1,7 +0,0 @@ -You've been invited to join "{{ org_name }}" on Browsertrix Cloud -~~~ -You are invited by {{ sender }} to join their organization, "{{ org_name }}" on Browsertrix Cloud! - -You can join by clicking here: {{ origin }}/invite/accept/{{ token }}?email={receiver_email} - -The invite token is: {{ token }} diff --git a/chart/email-templates/password_reset b/chart/email-templates/password_reset index ec00389f28..7d92180ecb 100644 --- a/chart/email-templates/password_reset +++ b/chart/email-templates/password_reset @@ -1,4 +1,10 @@ Password Reset ~~~ -We received your password reset request. Please click here: {{ origin }}/reset-password?token={{ token }} -to create a new password. +We received your password reset request. + +If you were locked out of your account, this request is sent automatically. + +If you did not attempt to log in and did not request this email, please let us know immediately at: +{{ support_email }} + +Please click here: {{ origin }}/reset-password?token={{ token }} to create a new password. diff --git a/chart/templates/configmap.yaml b/chart/templates/configmap.yaml index 17cd4c1574..1152bf5b46 100644 --- a/chart/templates/configmap.yaml +++ b/chart/templates/configmap.yaml @@ -48,8 +48,6 @@ data: STORAGES_JSON: "/ops-configs/storages.json" - SUPPORT_EMAIL: "{{ .Values.email.support_email }}" - --- apiVersion: v1 kind: ConfigMap diff --git a/chart/templates/secrets.yaml b/chart/templates/secrets.yaml index 9d511ad674..6f9f936121 100644 --- a/chart/templates/secrets.yaml +++ b/chart/templates/secrets.yaml @@ -15,6 +15,7 @@ stringData: EMAIL_REPLY_TO: "{{ .Values.email.reply_to }}" EMAIL_PASSWORD: "{{ .Values.email.password }}" EMAIL_SMTP_USE_TLS: "{{ .Values.email.use_tls }}" + EMAIL_SUPPORT: "{{ .Values.email.support_email }}" SUPERUSER_EMAIL: "{{ .Values.superuser.email }}" SUPERUSER_PASSWORD: "{{ .Values.superuser.password }}"