mirror of https://github.com/kortix-ai/suna.git
Merge branch 'PRODUCTION' into sync/production
This commit is contained in:
commit
ec09c6d149
|
@ -22,6 +22,8 @@ from services import billing as billing_api
|
||||||
from services import transcription as transcription_api
|
from services import transcription as transcription_api
|
||||||
from services.mcp_custom import discover_custom_tools
|
from services.mcp_custom import discover_custom_tools
|
||||||
import sys
|
import sys
|
||||||
|
from services import email_api
|
||||||
|
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
@ -136,6 +138,8 @@ app.include_router(mcp_api.router, prefix="/api")
|
||||||
|
|
||||||
app.include_router(transcription_api.router, prefix="/api")
|
app.include_router(transcription_api.router, prefix="/api")
|
||||||
|
|
||||||
|
app.include_router(email_api.router, prefix="/api")
|
||||||
|
|
||||||
@app.get("/api/health")
|
@app.get("/api/health")
|
||||||
async def health_check():
|
async def health_check():
|
||||||
"""Health check endpoint to verify API is working."""
|
"""Health check endpoint to verify API is working."""
|
||||||
|
|
|
@ -10,7 +10,7 @@ services:
|
||||||
memory: 32G
|
memory: 32G
|
||||||
|
|
||||||
worker:
|
worker:
|
||||||
command: python -m dramatiq --processes 20 --threads 16 run_agent_background
|
command: python -m dramatiq --processes 10 --threads 32 run_agent_background
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
|
|
|
@ -543,6 +543,27 @@ files = [
|
||||||
{file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"},
|
{file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dnspython"
|
||||||
|
version = "2.7.0"
|
||||||
|
description = "DNS toolkit"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"},
|
||||||
|
{file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"]
|
||||||
|
dnssec = ["cryptography (>=43)"]
|
||||||
|
doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"]
|
||||||
|
doq = ["aioquic (>=1.0.0)"]
|
||||||
|
idna = ["idna (>=3.7)"]
|
||||||
|
trio = ["trio (>=0.23)"]
|
||||||
|
wmi = ["wmi (>=1.5.1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dramatiq"
|
name = "dramatiq"
|
||||||
version = "1.17.1"
|
version = "1.17.1"
|
||||||
|
@ -605,6 +626,22 @@ attrs = ">=21.3.0"
|
||||||
e2b = ">=1.3.1,<2.0.0"
|
e2b = ">=1.3.1,<2.0.0"
|
||||||
httpx = ">=0.20.0,<1.0.0"
|
httpx = ">=0.20.0,<1.0.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "email-validator"
|
||||||
|
version = "2.2.0"
|
||||||
|
description = "A robust email address syntax and deliverability validation library."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"},
|
||||||
|
{file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
dnspython = ">=2.0.0"
|
||||||
|
idna = ">=2.0.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "entrypoints"
|
name = "entrypoints"
|
||||||
version = "0.4"
|
version = "0.4"
|
||||||
|
@ -1297,6 +1334,21 @@ tokenizers = "*"
|
||||||
extra-proxy = ["azure-identity (>=1.15.0,<2.0.0)", "azure-keyvault-secrets (>=4.8.0,<5.0.0)", "google-cloud-kms (>=2.21.3,<3.0.0)", "prisma (==0.11.0)", "redisvl (>=0.4.1,<0.5.0) ; python_version >= \"3.9\" and python_version < \"3.14\"", "resend (>=0.8.0,<0.9.0)"]
|
extra-proxy = ["azure-identity (>=1.15.0,<2.0.0)", "azure-keyvault-secrets (>=4.8.0,<5.0.0)", "google-cloud-kms (>=2.21.3,<3.0.0)", "prisma (==0.11.0)", "redisvl (>=0.4.1,<0.5.0) ; python_version >= \"3.9\" and python_version < \"3.14\"", "resend (>=0.8.0,<0.9.0)"]
|
||||||
proxy = ["PyJWT (>=2.8.0,<3.0.0)", "apscheduler (>=3.10.4,<4.0.0)", "backoff", "boto3 (==1.34.34)", "cryptography (>=43.0.1,<44.0.0)", "fastapi (>=0.115.5,<0.116.0)", "fastapi-sso (>=0.16.0,<0.17.0)", "gunicorn (>=23.0.0,<24.0.0)", "litellm-proxy-extras (==0.1.7)", "mcp (==1.5.0) ; python_version >= \"3.10\"", "orjson (>=3.9.7,<4.0.0)", "pynacl (>=1.5.0,<2.0.0)", "python-multipart (>=0.0.18,<0.0.19)", "pyyaml (>=6.0.1,<7.0.0)", "rq", "uvicorn (>=0.29.0,<0.30.0)", "uvloop (>=0.21.0,<0.22.0)", "websockets (>=13.1.0,<14.0.0)"]
|
proxy = ["PyJWT (>=2.8.0,<3.0.0)", "apscheduler (>=3.10.4,<4.0.0)", "backoff", "boto3 (==1.34.34)", "cryptography (>=43.0.1,<44.0.0)", "fastapi (>=0.115.5,<0.116.0)", "fastapi-sso (>=0.16.0,<0.17.0)", "gunicorn (>=23.0.0,<24.0.0)", "litellm-proxy-extras (==0.1.7)", "mcp (==1.5.0) ; python_version >= \"3.10\"", "orjson (>=3.9.7,<4.0.0)", "pynacl (>=1.5.0,<2.0.0)", "python-multipart (>=0.0.18,<0.0.19)", "pyyaml (>=6.0.1,<7.0.0)", "rq", "uvicorn (>=0.29.0,<0.30.0)", "uvloop (>=0.21.0,<0.22.0)", "websockets (>=13.1.0,<14.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mailtrap"
|
||||||
|
version = "2.1.0"
|
||||||
|
description = "Official mailtrap.io API client"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "mailtrap-2.1.0-py3-none-any.whl", hash = "sha256:6cef8dc02734e3e3a16161e38d184ea6971e925673c731c8ac968b88556f069e"},
|
||||||
|
{file = "mailtrap-2.1.0.tar.gz", hash = "sha256:22fccf3cd912a7e47d4a1bb86865cf0f0587d59dc73bc78d9e77d596767f5b85"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
requests = ">=2.26.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markupsafe"
|
name = "markupsafe"
|
||||||
version = "3.0.2"
|
version = "3.0.2"
|
||||||
|
@ -3719,4 +3771,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"]
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "ed0ccb92ccc81ecff536968c883a2ea96d2ee8a6c06a18d0023b0cd4185f8a28"
|
content-hash = "70ef4a9be6ddd82debb9e9e377d9f47f6f6917b43b75a688e404adeef9e61018"
|
||||||
|
|
|
@ -56,6 +56,10 @@ langfuse = "^2.60.5"
|
||||||
Pillow = "^10.0.0"
|
Pillow = "^10.0.0"
|
||||||
mcp = "^1.0.0"
|
mcp = "^1.0.0"
|
||||||
sentry-sdk = {extras = ["fastapi"], version = "^2.29.1"}
|
sentry-sdk = {extras = ["fastapi"], version = "^2.29.1"}
|
||||||
|
httpx = "^0.28.0"
|
||||||
|
aiohttp = "^3.9.0"
|
||||||
|
email-validator = "^2.0.0"
|
||||||
|
mailtrap = "^2.0.1"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
agentpress = "agentpress.cli:main"
|
agentpress = "agentpress.cli:main"
|
||||||
|
|
|
@ -38,4 +38,6 @@ Pillow>=10.0.0
|
||||||
sentry-sdk[fastapi]>=2.29.1
|
sentry-sdk[fastapi]>=2.29.1
|
||||||
mcp>=1.0.0
|
mcp>=1.0.0
|
||||||
mcp_use>=1.0.0
|
mcp_use>=1.0.0
|
||||||
aiohttp>=3.9.0
|
aiohttp>=3.9.0
|
||||||
|
email-validator>=2.0.0
|
||||||
|
mailtrap>=2.0.1
|
||||||
|
|
|
@ -132,6 +132,8 @@ async def run_agent_background(
|
||||||
final_status = "running"
|
final_status = "running"
|
||||||
error_message = None
|
error_message = None
|
||||||
|
|
||||||
|
pending_redis_operations = []
|
||||||
|
|
||||||
async for response in agent_gen:
|
async for response in agent_gen:
|
||||||
if stop_signal_received:
|
if stop_signal_received:
|
||||||
logger.info(f"Agent run {agent_run_id} stopped by signal.")
|
logger.info(f"Agent run {agent_run_id} stopped by signal.")
|
||||||
|
@ -141,8 +143,8 @@ async def run_agent_background(
|
||||||
|
|
||||||
# Store response in Redis list and publish notification
|
# Store response in Redis list and publish notification
|
||||||
response_json = json.dumps(response)
|
response_json = json.dumps(response)
|
||||||
asyncio.create_task(redis.rpush(response_list_key, response_json))
|
pending_redis_operations.append(asyncio.create_task(redis.rpush(response_list_key, response_json)))
|
||||||
asyncio.create_task(redis.publish(response_channel, "new"))
|
pending_redis_operations.append(asyncio.create_task(redis.publish(response_channel, "new")))
|
||||||
total_responses += 1
|
total_responses += 1
|
||||||
|
|
||||||
# Check for agent-signaled completion or error
|
# Check for agent-signaled completion or error
|
||||||
|
@ -239,8 +241,11 @@ async def run_agent_background(
|
||||||
# Remove the instance-specific active run key
|
# Remove the instance-specific active run key
|
||||||
await _cleanup_redis_instance_key(agent_run_id)
|
await _cleanup_redis_instance_key(agent_run_id)
|
||||||
|
|
||||||
# Wait for 5 seconds for any pending redis operations to complete
|
# Wait for all pending redis operations to complete, with timeout
|
||||||
await asyncio.sleep(5)
|
try:
|
||||||
|
await asyncio.wait_for(asyncio.gather(*pending_redis_operations), timeout=5.0)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
logger.warning(f"Timeout waiting for pending Redis operations for {agent_run_id}")
|
||||||
|
|
||||||
logger.info(f"Agent run background task fully completed for: {agent_run_id} (Instance: {instance_id}) with final status: {final_status}")
|
logger.info(f"Agent run background task fully completed for: {agent_run_id} (Instance: {instance_id}) with final status: {final_status}")
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
import mailtrap as mt
|
||||||
|
from utils.config import config
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class EmailService:
|
||||||
|
def __init__(self):
|
||||||
|
self.api_token = os.getenv('MAILTRAP_API_TOKEN')
|
||||||
|
self.sender_email = os.getenv('MAILTRAP_SENDER_EMAIL', 'dom@kortix.ai')
|
||||||
|
self.sender_name = os.getenv('MAILTRAP_SENDER_NAME', 'Suna Team')
|
||||||
|
|
||||||
|
if not self.api_token:
|
||||||
|
logger.warning("MAILTRAP_API_TOKEN not found in environment variables")
|
||||||
|
self.client = None
|
||||||
|
else:
|
||||||
|
self.client = mt.MailtrapClient(token=self.api_token)
|
||||||
|
|
||||||
|
def send_welcome_email(self, user_email: str, user_name: Optional[str] = None) -> bool:
|
||||||
|
if not self.client:
|
||||||
|
logger.error("Cannot send email: MAILTRAP_API_TOKEN not configured")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not user_name:
|
||||||
|
user_name = user_email.split('@')[0].title()
|
||||||
|
|
||||||
|
subject = "🎉 Welcome to Suna — Let's Get Started "
|
||||||
|
html_content = self._get_welcome_email_template(user_name)
|
||||||
|
text_content = self._get_welcome_email_text(user_name)
|
||||||
|
|
||||||
|
return self._send_email(
|
||||||
|
to_email=user_email,
|
||||||
|
to_name=user_name,
|
||||||
|
subject=subject,
|
||||||
|
html_content=html_content,
|
||||||
|
text_content=text_content
|
||||||
|
)
|
||||||
|
|
||||||
|
def _send_email(
|
||||||
|
self,
|
||||||
|
to_email: str,
|
||||||
|
to_name: str,
|
||||||
|
subject: str,
|
||||||
|
html_content: str,
|
||||||
|
text_content: str
|
||||||
|
) -> bool:
|
||||||
|
try:
|
||||||
|
mail = mt.Mail(
|
||||||
|
sender=mt.Address(email=self.sender_email, name=self.sender_name),
|
||||||
|
to=[mt.Address(email=to_email, name=to_name)],
|
||||||
|
subject=subject,
|
||||||
|
text=text_content,
|
||||||
|
html=html_content,
|
||||||
|
category="welcome"
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.send(mail)
|
||||||
|
|
||||||
|
logger.info(f"Welcome email sent to {to_email}. Response: {response}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error sending email to {to_email}: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_welcome_email_template(self, user_name: str) -> str:
|
||||||
|
return f"""<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Welcome to Kortix Suna</title>
|
||||||
|
<style>
|
||||||
|
body {{
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #000000;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}}
|
||||||
|
.container {{
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 40px auto;
|
||||||
|
padding: 30px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}}
|
||||||
|
.logo-container {{
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
padding: 10px 0;
|
||||||
|
}}
|
||||||
|
.logo {{
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 60px;
|
||||||
|
display: inline-block;
|
||||||
|
}}
|
||||||
|
h1 {{
|
||||||
|
font-size: 24px;
|
||||||
|
color: #000000;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}}
|
||||||
|
p {{
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}}
|
||||||
|
a {{
|
||||||
|
color: #3366cc;
|
||||||
|
text-decoration: none;
|
||||||
|
}}
|
||||||
|
a:hover {{
|
||||||
|
text-decoration: underline;
|
||||||
|
}}
|
||||||
|
.button {{
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 30px;
|
||||||
|
background-color: #3B82F6;
|
||||||
|
color: white !important;
|
||||||
|
padding: 14px 24px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: none;
|
||||||
|
}}
|
||||||
|
.button:hover {{
|
||||||
|
background-color: #2563EB;
|
||||||
|
text-decoration: none;
|
||||||
|
}}
|
||||||
|
.emoji {{
|
||||||
|
font-size: 20px;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="logo-container">
|
||||||
|
<img src="https://i.postimg.cc/WdNtRx5Z/kortix-suna-logo.png" alt="Kortix Suna Logo" class="logo">
|
||||||
|
</div>
|
||||||
|
<h1>Welcome to Kortix Suna!</h1>
|
||||||
|
|
||||||
|
<p>Hi {user_name},</p>
|
||||||
|
|
||||||
|
<p><em><strong>Welcome to Kortix Suna — we're excited to have you on board!</strong></em></p>
|
||||||
|
|
||||||
|
<p>To get started, we'd like to get to know you better: fill out this short <a href="https://docs.google.com/forms/d/e/1FAIpQLSef1EHuqmIh_iQz-kwhjnzSC3Ml-V_5wIySDpMoMU9W_j24JQ/viewform">form</a>!</p>
|
||||||
|
|
||||||
|
<p>To celebrate your arrival, here's a <strong>15% discount</strong> to try out the best version of Suna (1 month):</p>
|
||||||
|
|
||||||
|
<p>🎁 Use code <strong>WELCOME15</strong> at checkout.</p>
|
||||||
|
|
||||||
|
<p>Let us know if you need help getting started or have questions — we're always here, and join our <a href="https://discord.com/invite/FjD644cfcs">Discord community</a>.</p>
|
||||||
|
|
||||||
|
<p>Thanks again, and welcome to the Suna community <span class="emoji">🌞</span></p>
|
||||||
|
|
||||||
|
<p>— The Suna Team</p>
|
||||||
|
|
||||||
|
<a href="https://www.suna.so/" class="button">Go to the platform</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
|
||||||
|
def _get_welcome_email_text(self, user_name: str) -> str:
|
||||||
|
return f"""Hi {user_name},
|
||||||
|
|
||||||
|
Welcome to Suna — we're excited to have you on board!
|
||||||
|
|
||||||
|
To get started, we'd like to get to know you better: fill out this short form!
|
||||||
|
https://docs.google.com/forms/d/e/1FAIpQLSef1EHuqmIh_iQz-kwhjnzSC3Ml-V_5wIySDpMoMU9W_j24JQ/viewform
|
||||||
|
|
||||||
|
To celebrate your arrival, here's a 15% discount to try out the best version of Suna (1 month):
|
||||||
|
🎁 Use code WELCOME15 at checkout.
|
||||||
|
|
||||||
|
Let us know if you need help getting started or have questions — we're always here, and join our Discord community: https://discord.com/invite/FjD644cfcs
|
||||||
|
|
||||||
|
Thanks again, and welcome to the Suna community 🌞
|
||||||
|
|
||||||
|
— The Suna Team
|
||||||
|
|
||||||
|
Go to the platform: https://www.suna.so/
|
||||||
|
|
||||||
|
---
|
||||||
|
© 2024 Suna. All rights reserved.
|
||||||
|
You received this email because you signed up for a Suna account."""
|
||||||
|
|
||||||
|
email_service = EmailService()
|
|
@ -0,0 +1,70 @@
|
||||||
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
from pydantic import BaseModel, EmailStr
|
||||||
|
from typing import Optional
|
||||||
|
import asyncio
|
||||||
|
from services.email import email_service
|
||||||
|
from utils.logger import logger
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
class SendWelcomeEmailRequest(BaseModel):
|
||||||
|
email: EmailStr
|
||||||
|
name: Optional[str] = None
|
||||||
|
|
||||||
|
class EmailResponse(BaseModel):
|
||||||
|
success: bool
|
||||||
|
message: str
|
||||||
|
|
||||||
|
@router.post("/send-welcome-email", response_model=EmailResponse)
|
||||||
|
async def send_welcome_email(request: SendWelcomeEmailRequest):
|
||||||
|
try:
|
||||||
|
logger.info(f"Sending welcome email to {request.email}")
|
||||||
|
success = email_service.send_welcome_email(
|
||||||
|
user_email=request.email,
|
||||||
|
user_name=request.name
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
return EmailResponse(
|
||||||
|
success=True,
|
||||||
|
message="Welcome email sent successfully"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return EmailResponse(
|
||||||
|
success=False,
|
||||||
|
message="Failed to send welcome email"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error sending welcome email to {request.email}: {str(e)}")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail="Internal server error while sending email"
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.post("/send-welcome-email-background", response_model=EmailResponse)
|
||||||
|
async def send_welcome_email_background(request: SendWelcomeEmailRequest):
|
||||||
|
try:
|
||||||
|
logger.info(f"Queuing welcome email for {request.email}")
|
||||||
|
|
||||||
|
def send_email():
|
||||||
|
return email_service.send_welcome_email(
|
||||||
|
user_email=request.email,
|
||||||
|
user_name=request.name
|
||||||
|
)
|
||||||
|
|
||||||
|
import concurrent.futures
|
||||||
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||||
|
future = executor.submit(send_email)
|
||||||
|
|
||||||
|
return EmailResponse(
|
||||||
|
success=True,
|
||||||
|
message="Welcome email queued for sending"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error queuing welcome email for {request.email}: {str(e)}")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail="Internal server error while queuing email"
|
||||||
|
)
|
|
@ -3,6 +3,30 @@
|
||||||
import { createClient } from '@/lib/supabase/server';
|
import { createClient } from '@/lib/supabase/server';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
|
async function sendWelcomeEmail(email: string, name?: string) {
|
||||||
|
try {
|
||||||
|
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
|
||||||
|
const response = await fetch(`${backendUrl}/send-welcome-email-background`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
console.log(`Welcome email queued for ${email}`);
|
||||||
|
} else {
|
||||||
|
console.error(`Failed to queue welcome email for ${email}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending welcome email:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function signIn(prevState: any, formData: FormData) {
|
export async function signIn(prevState: any, formData: FormData) {
|
||||||
const email = formData.get('email') as string;
|
const email = formData.get('email') as string;
|
||||||
const password = formData.get('password') as string;
|
const password = formData.get('password') as string;
|
||||||
|
@ -64,12 +88,17 @@ export async function signUp(prevState: any, formData: FormData) {
|
||||||
return { message: error.message || 'Could not create account' };
|
return { message: error.message || 'Could not create account' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to sign in immediately
|
const userName = email.split('@')[0].replace(/[._-]/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
||||||
const { error: signInError } = await supabase.auth.signInWithPassword({
|
|
||||||
|
const { error: signInError, data: signInData } = await supabase.auth.signInWithPassword({
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (signInData) {
|
||||||
|
sendWelcomeEmail(email, userName);
|
||||||
|
}
|
||||||
|
|
||||||
if (signInError) {
|
if (signInError) {
|
||||||
return {
|
return {
|
||||||
message:
|
message:
|
||||||
|
|
Loading…
Reference in New Issue