Skip to content

Commit

Permalink
Send welcome emails when a new user signs up (#691)
Browse files Browse the repository at this point in the history
* Don't trigger any re-indexing on server initailization

* Integrate Resend to send welcome emails when a new user signs up

- Only send if this is the first time they've signed in
- Configure welcome email with basic styling, as more complex designs don't work and style tag did not work
  • Loading branch information
sabaimran authored Apr 10, 2024
1 parent 6d15302 commit 3fe94a6
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 18 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ prod = [
"stripe == 7.3.0",
"twilio == 8.11",
"boto3 >= 1.34.57",
"resend >= 0.8.0",
]
dev = [
"khoj-assistant[prod]",
Expand Down
2 changes: 1 addition & 1 deletion src/khoj/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
# https://docs.djangoproject.com/en/4.2/howto/static-files/

STATIC_ROOT = BASE_DIR / "static"
STATICFILES_DIRS = [BASE_DIR / "interface/web"]
STATICFILES_DIRS = [BASE_DIR / "interface/web", BASE_DIR / "interface/email"]
STATIC_URL = "/static/"

# Default primary key field type
Expand Down
30 changes: 15 additions & 15 deletions src/khoj/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,10 @@ def configure_server(
state.SearchType = configure_search_types()
state.search_models = configure_search(state.search_models, state.config.search_type)
setup_default_agent()
initialize_content(regenerate, search_type, init, user)

if not init:
initialize_content(regenerate, search_type, user)

except Exception as e:
raise e

Expand All @@ -240,23 +243,20 @@ def setup_default_agent():
AgentAdapters.create_default_agent()


def initialize_content(regenerate: bool, search_type: Optional[SearchType] = None, init=False, user: KhojUser = None):
def initialize_content(regenerate: bool, search_type: Optional[SearchType] = None, user: KhojUser = None):
# Initialize Content from Config
if state.search_models:
try:
if init:
logger.info("📬 No-op...")
else:
logger.info("📬 Updating content index...")
all_files = collect_files(user=user)
status = configure_content(
all_files,
regenerate,
search_type,
user=user,
)
if not status:
raise RuntimeError("Failed to update content index")
logger.info("📬 Updating content index...")
all_files = collect_files(user=user)
status = configure_content(
all_files,
regenerate,
search_type,
user=user,
)
if not status:
raise RuntimeError("Failed to update content index")
except Exception as e:
raise e

Expand Down
61 changes: 61 additions & 0 deletions src/khoj/interface/email/welcome.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Khoj</title>
</head>
<body>
<body style="font-family: 'Verdana', sans-serif; font-weight: 400; font-style: normal; padding: 0; text-align: left; width: 600px; margin: 20px auto;">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<a class="logo" href="https://khoj.dev" target="_blank" style="text-decoration: none; text-decoration: underline dotted;">
<img src="https://khoj.dev/khoj-logo-sideways-500.png" alt="Khoj Logo" style="width: 100px;">
</a>
<div class="calls-to-action" style="margin-top: 20px;">
<div>
<h1 style="color: #333; font-size: large; font-weight: bold; margin: 0; line-height: 1.5; background-color: #fee285; padding: 8px; box-shadow: 6px 6px rgba(0, 0, 0, 1.5);">Merge AI with your brain</h1>
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">Hi {{name}}! We are psyched to be part of your journey with personal AI. To better help you, we're committed to staying transparent, accessible, and completely open-source.</p>
<a class="button" href="https://app.khoj.dev" target="_blank" style="display: block; width: 200px; text-align: center; padding: 10px; margin-top: 20px; color: #333; background-color: #fee285; text-decoration: none; border-radius: 5px; font-weight: bold; transition: background-color 0.3s ease; box-shadow: 6px 6px rgba(0, 0, 0, 1.0); padding: 4px; font-size: large; text-transform: uppercase;">Get Started</a>
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">You're about to get a whole lot more more productive.</p>

<div style="display: grid; grid-template-columns: 1fr 1fr; grid-gap: 12px; margin-top: 20px;">
<div style="border: 1px solid black; border-radius: 8px; padding: 8px; box-shadow: 6px 6px rgba(0, 0, 0, 1.0); margin-top: 20px;">
<a href="https://docs.khoj.dev/features/online_search" style="text-decoration: none; text-decoration: underline dotted;">
<h3 style="color: #333; font-size: large; margin: 0; padding: 0; line-height: 2.0; background-color: #b8f1c7; padding: 8px; ">Ditch the search bar</h3>
</a>
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">You don't need to click around Google results and sift through information yourself, because Khoj is connected to the internet.</p>
</div>
<div style="border: 1px solid black; border-radius: 8px; padding: 8px; box-shadow: 6px 6px rgba(0, 0, 0, 1.0); margin-top: 20px;">
<a href="https://app.khoj.dev/agents" style="text-decoration: none; text-decoration: underline dotted;">
<h3 style="color: #333; font-size: large; margin: 0; padding: 0; line-height: 2.0; background-color: #b8f1c7; padding: 8px;">Get a village, not just an agent</h3>
</a>
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">Khoj can fill the need for more specialized assistance, <a href="https://blog.khoj.dev/posts/using-khoj-for-studying/">such as tutoring</a>, with its curated agents. You get a whole team, always available.</p>
</div>
<div style="border: 1px solid black; border-radius: 8px; padding: 8px; box-shadow: 6px 6px rgba(0, 0, 0, 1.0); margin-top: 20px;">
<a href="https://docs.khoj.dev/category/clients" style="text-decoration: none; text-decoration: underline dotted;">
<h3 style="color: #333; font-size: large; margin: 0; padding: 0; line-height: 2.0; background-color: #b8f1c7; padding: 8px;">Available where you are</h3>
</a>
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">Build on top of your digital brain. Khoj stores whatever data you share with it, so you can get answers from your personal notes and documents in your native language. You can engage from your desktop, Obsidian, WhatsApp, or the web.</p>
</div>
<div style="border: 1px solid black; border-radius: 8px; padding: 8px; box-shadow: 6px 6px rgba(0, 0, 0, 1.0); margin-top: 20px;">
<a href="https://blog.khoj.dev/posts/how-khoj-generates-images/" style="text-decoration: none; text-decoration: underline dotted;">
<h3 style="color: #333; font-size: large; margin: 0; padding: 0; line-height: 2.0; background-color: #b8f1c7; padding: 8px;">Create rich, contextual images</h3>
</a>
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">With your shared data, Khoj can help you create astoundingly personal images depicting scenes of what's important to you.</p>
</div>
</div>
</div>
</div>
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">Like something? Dislike something? Searching for some other magical feature? Our inbox is always open for feedback! Contact us on team@khoj.dev at anytime.</p>

<p style="color: #333; font-size: large; margin-top: 20px; padding: 0; line-height: 1.5;">- The Khoj Team</p>
<table style="width: 100%; margin-top: 20px;">
<tr>
<td style="text-align: center;"><a href="https://docs.khoj.dev" target="_blank" style="padding: 8px; color: #333; background-color: #fee285; border-radius: 8px; box-shadow: 6px 6px rgba(0, 0, 0, 1.0);">Docs</a></td>
<td style="text-align: center;"><a href="https://github.com/khoj-ai/khoj" target="_blank" style="padding: 8px; color: #333; background-color: #fee285; border-radius: 8px; box-shadow: 6px 6px rgba(0, 0, 0, 1.0);">GitHub</a></td>
<td style="text-align: center;"><a href="https://twitter.com/khoj_ai" target="_blank" style="padding: 8px; color: #333; background-color: #fee285; border-radius: 8px; box-shadow: 6px 6px rgba(0, 0, 0, 1.0);">Twitter</a></td>
<td style="text-align: center;"><a href="https://www.linkedin.com/company/khoj-ai" target="_blank" style="padding: 8px; color: #333; background-color: #fee285; border-radius: 8px; box-shadow: 6px 6px rgba(0, 0, 0, 1.0);">LinkedIn</a></td>
<td style="text-align: center;"><a href="https://discord.gg/BDgyabRM6e" target="_blank" style="padding: 8px; color: #333; background-color: #fee285; border-radius: 8px; box-shadow: 6px 6px rgba(0, 0, 0, 1.0);">Discord</a></td>
</tr>
</table>

</body>
</html>
2 changes: 1 addition & 1 deletion src/khoj/routers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def update(
logger.warning(error_msg)
raise HTTPException(status_code=500, detail=error_msg)
try:
initialize_content(regenerate=force, search_type=t, init=False, user=user)
initialize_content(regenerate=force, search_type=t, user=user)
except Exception as e:
error_msg = f"🚨 Failed to update server via API: {e}"
logger.error(error_msg, exc_info=True)
Expand Down
7 changes: 6 additions & 1 deletion src/khoj/routers/auth.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import asyncio
import datetime
import logging
import os
from typing import Optional
Expand All @@ -15,6 +17,7 @@
get_khoj_tokens,
get_or_create_user,
)
from khoj.routers.email import send_welcome_email
from khoj.routers.helpers import update_telemetry_state
from khoj.utils import state

Expand Down Expand Up @@ -110,10 +113,12 @@ async def auth(request: Request):
except OAuthError as error:
return HTMLResponse(f"<h1>{error.error}</h1>")
khoj_user = await get_or_create_user(idinfo)

if khoj_user:
request.session["user"] = dict(idinfo)

if not khoj_user.last_login:
if datetime.timedelta(minutes=3) > (datetime.datetime.now(datetime.UTC) - khoj_user.date_joined):
asyncio.create_task(send_welcome_email(idinfo["name"], idinfo["email"]))
update_telemetry_state(
request=request,
telemetry_type="api",
Expand Down
38 changes: 38 additions & 0 deletions src/khoj/routers/email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import logging
import os

import resend
from django.conf import settings
from jinja2 import Environment, FileSystemLoader

from khoj.utils.helpers import is_none_or_empty

logger = logging.getLogger(__name__)


RESEND_API_KEY = os.getenv("RESEND_API_KEY")

static_files = os.path.join(settings.BASE_DIR, "static")

env = Environment(loader=FileSystemLoader(static_files))

if not RESEND_API_KEY:
logger.info("RESEND_API_KEY not set - email sending disabled")


resend.api_key = RESEND_API_KEY


async def send_welcome_email(name, email):
template = env.get_template("welcome.html")

html_content = template.render(name=name if not is_none_or_empty(name) else "you")

r = resend.Emails.send(
{
"from": "team@khoj.dev",
"to": email,
"subject": f"Welcome to Khoj, {name}!" if name else "Welcome to Khoj!",
"html": html_content,
}
)

0 comments on commit 3fe94a6

Please sign in to comment.