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