feat: use MailGun instead of Resend

This commit is contained in:
rzmk 2025-07-03 19:01:24 -04:00
parent 5fd979b2a2
commit 3b60ec5547
9 changed files with 1628 additions and 54 deletions

View file

@ -73,8 +73,9 @@ LEARNHOUSE_REDIS_CONNECTION_STRING=redis://default:strong_redis_password@redis:6
LEARNHOUSE_CHROMADB_HOST=chromadb
# Email Configuration (optional)
LEARNHOUSE_EMAIL_PROVIDER=resend
LEARNHOUSE_RESEND_API_KEY=re_your_resend_api_key
LEARNHOUSE_MAILGUN_API_KEY=re_your_mailgun_api_key
LEARNHOUSE_MAILGUN_DOMAIN=re_your_mailgun_domain
LEARNHOUSE_SYSTEM_EMAIL_ADDRESS=re_your_system_email_address
```
### 5. Start the Build Process
@ -97,4 +98,4 @@ LEARNHOUSE_RESEND_API_KEY=re_your_resend_api_key
## Additional Resources
- [LearnHouse Documentation](https://docs.learnhouse.io/)
- [Coolify Documentation](https://coolify.io/docs)
- [Coolify Documentation](https://coolify.io/docs)

View file

@ -20,8 +20,8 @@ class SecurityConfig(BaseModel):
class ChromaDBConfig(BaseModel):
isSeparateDatabaseEnabled: bool | None
db_host: str | None
isSeparateDatabaseEnabled: bool | None
db_host: str | None
class AIConfig(BaseModel):
@ -53,7 +53,8 @@ class HostingConfig(BaseModel):
class MailingConfig(BaseModel):
resend_api_key: str
mailgun_api_key: str
mailgun_domain: str
system_email_address: str
@ -151,7 +152,7 @@ def get_learnhouse_config() -> LearnHouseConfig:
env_self_hosted = os.environ.get("LEARNHOUSE_SELF_HOSTED")
env_sql_connection_string = os.environ.get("LEARNHOUSE_SQL_CONNECTION_STRING")
# Fill in values with YAML file if they are not provided
site_name = env_site_name or yaml_config.get("site_name")
@ -236,10 +237,14 @@ def get_learnhouse_config() -> LearnHouseConfig:
).get("redis_connection_string")
# Mailing config
env_resend_api_key = os.environ.get("LEARNHOUSE_RESEND_API_KEY")
env_mailgun_api_key = os.environ.get("LEARNHOUSE_MAILGUN_API_KEY")
env_mailgun_domain = os.environ.get("LEARNHOUSE_MAILGUN_DOMAIN")
env_system_email_address = os.environ.get("LEARNHOUSE_SYSTEM_EMAIL_ADDRESS")
resend_api_key = env_resend_api_key or yaml_config.get("mailing_config", {}).get(
"resend_api_key"
mailgun_api_key = env_mailgun_api_key or yaml_config.get("mailing_config", {}).get(
"mailgun_api_key"
)
mailgun_domain = env_mailgun_domain or yaml_config.get("mailing_domain", {}).get(
"mailgun_domain"
)
system_email_address = env_system_email_address or yaml_config.get(
"mailing_config", {}
@ -251,11 +256,11 @@ def get_learnhouse_config() -> LearnHouseConfig:
env_stripe_webhook_standard_secret = os.environ.get("LEARNHOUSE_STRIPE_WEBHOOK_STANDARD_SECRET")
env_stripe_webhook_connect_secret = os.environ.get("LEARNHOUSE_STRIPE_WEBHOOK_CONNECT_SECRET")
env_stripe_client_id = os.environ.get("LEARNHOUSE_STRIPE_CLIENT_ID")
stripe_secret_key = env_stripe_secret_key or yaml_config.get("payments_config", {}).get(
"stripe", {}
).get("stripe_secret_key")
stripe_publishable_key = env_stripe_publishable_key or yaml_config.get("payments_config", {}).get(
"stripe", {}
).get("stripe_publishable_key")
@ -313,7 +318,7 @@ def get_learnhouse_config() -> LearnHouseConfig:
ai_config=ai_config,
redis_config=RedisConfig(redis_connection_string=redis_connection_string),
mailing_config=MailingConfig(
resend_api_key=resend_api_key, system_email_address=system_email_address
mailgun_api_key=mailgun_api_key, mailgun_domain=mailgun_domain, system_email_address=system_email_address
),
payments_config=InternalPaymentsConfig(
stripe=InternalStripeConfig(

View file

@ -29,7 +29,8 @@ hosting_config:
endpoint_url: ""
mailing_config:
resend_api_key: ""
mailgun_api_key: ""
mailgun_domain: ""
system_email_adress: ""
database_config:

View file

@ -27,7 +27,6 @@ dependencies = [
"pyyaml>=6.0.1",
"redis>=5.0.7",
"requests>=2.32.3",
"resend>=2.4.0",
"sqlmodel>=0.0.19",
"tiktoken>=0.7.0",
"uvicorn==0.30.1",

View file

@ -1,19 +1,23 @@
from pydantic import EmailStr
import resend
from config.config import get_learnhouse_config
from mailgun.client import Client
def send_email(to: EmailStr, subject: str, body: str):
lh_config = get_learnhouse_config()
params = {
"from": "LearnHouse <" + lh_config.mailing_config.system_email_address + ">",
"to": [to],
mailgun_api_key = lh_config.mailing_config.mailgun_api_key
mailgun_domain = lh_config.mailing_config.mailgun_domain
mailgun_client: Client = Client(auth=("api", mailgun_api_key))
data = {
"from": "Learn with Mueez <" + lh_config.mailing_config.system_email_address + ">",
"to": to,
"subject": subject,
"html": body,
}
resend.api_key = lh_config.mailing_config.resend_api_key
email = resend.Emails.send(params)
return email
# Send email
req = mailgun_client.messages.create(data=data, domain=mailgun_domain)
if id in req:
return True
return False

View file

@ -11,13 +11,12 @@ def send_account_creation_email(
# send email
return send_email(
to=email,
subject=f"Welcome to LearnHouse, {user.username}!",
subject=f"Welcome to Learn with Mueez, {user.username}!",
body=f"""
<html>
<body>
<p>Hello {user.username}</p>
<p>Welcome to LearnHouse! , get started by creating your own organization or join a one.</p>
<p>Need some help to get started ? <a href="https://university.learnhouse.io">LearnHouse Academy</a></p>
<p>Welcome to Learn with Mueez {user.username}! Get started by logging in at <a href="https://learn.mueezkhan.com">learn.mueezkhan.com</a> and starting a course.</p>
<p>If you need any help, please reach out at <a href="https://mueezkhan.com/contact">mueezkhan.com/contact</a>.</p>
</body>
</html>
""",
@ -30,7 +29,7 @@ def send_password_reset_email(
organization: OrganizationRead,
email: EmailStr,
):
# send email
return send_email(
to=email,
@ -38,10 +37,11 @@ def send_password_reset_email(
body=f"""
<html>
<body>
<p>Hello {user.username}</p>
<p>Hey {user.username}!</p>
<p>You have requested to reset your password.</p>
<p>Here is your reset code: {generated_reset_code}</p>
<p>Click <a href="https://{organization.slug}.learnhouse.io/reset?orgslug={organization.slug}&email={email}&resetCode={generated_reset_code}">here</a> to reset your password.</p>
<p>If you need any help, please reach out at <a href="https://mueezkhan.com/contact">mueezkhan.com/contact</a>.</p>
</body>
</html>
""",

40
apps/api/uv.lock generated
View file

@ -1,4 +1,5 @@
version = 1
revision = 1
requires-python = ">=3.12.3, <3.13.0"
resolution-markers = [
"python_full_version < '3.12.4'",
@ -311,7 +312,7 @@ name = "click"
version = "8.1.8"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "platform_system == 'Windows'" },
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
wheels = [
@ -1047,6 +1048,7 @@ dependencies = [
{ name = "langchain-openai", version = "0.1.14", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12.4'" },
{ name = "langchain-openai", version = "0.1.25", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12.4'" },
{ name = "logfire", extra = ["sqlalchemy"] },
{ name = "mailgun" },
{ name = "openai" },
{ name = "passlib" },
{ name = "psycopg2-binary" },
@ -1060,7 +1062,6 @@ dependencies = [
{ name = "pyyaml" },
{ name = "redis" },
{ name = "requests" },
{ name = "resend" },
{ name = "sqlalchemy-utils" },
{ name = "sqlmodel" },
{ name = "stripe" },
@ -1085,6 +1086,7 @@ requires-dist = [
{ name = "langchain-community", specifier = ">=0.0.20" },
{ name = "langchain-openai", specifier = ">=0.0.6" },
{ name = "logfire", extras = ["sqlalchemy"], specifier = ">=3.8.0" },
{ name = "mailgun", specifier = ">=1.0.2" },
{ name = "openai", specifier = ">=1.50.2" },
{ name = "passlib", specifier = ">=1.7.4" },
{ name = "psycopg2-binary", specifier = ">=2.9.9" },
@ -1098,7 +1100,6 @@ requires-dist = [
{ name = "pyyaml", specifier = ">=6.0.1" },
{ name = "redis", specifier = ">=5.0.7" },
{ name = "requests", specifier = ">=2.32.3" },
{ name = "resend", specifier = ">=2.4.0" },
{ name = "sqlalchemy-utils", specifier = ">=0.41.2" },
{ name = "sqlmodel", specifier = ">=0.0.19" },
{ name = "stripe", specifier = ">=11.1.1" },
@ -1130,6 +1131,18 @@ sqlalchemy = [
{ name = "opentelemetry-instrumentation-sqlalchemy" },
]
[[package]]
name = "mailgun"
version = "1.0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f4/37/daf8116c9937d97892efff08a2e34064dcfa2dd70065e12ec9366c1509a1/mailgun-1.0.2.tar.gz", hash = "sha256:5c7964426c823866e8de2b4e9062e2874494cfd92c59a0319996f573bea05507", size = 57593 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/26/bb/c97937ca4f75d221e5ff12d73a46c4fc770f26f942aad9fe09ff0aeea4d5/mailgun-1.0.2-py3-none-any.whl", hash = "sha256:79ac6c9e6c5166c97bd078756a278a19fcd3a9afaffc3747806026012b8343e3", size = 46536 },
]
[[package]]
name = "mako"
version = "1.3.9"
@ -1859,7 +1872,7 @@ wheels = [
[[package]]
name = "requests"
version = "2.32.3"
version = "2.32.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
@ -1867,9 +1880,9 @@ dependencies = [
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
{ url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847 },
]
[[package]]
@ -1897,19 +1910,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 },
]
[[package]]
name = "resend"
version = "2.6.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "requests" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/46/ff/f4235a93533cb04199ab50ef250afa514ada55a4a4b86e32e0c763931926/resend-2.6.0.tar.gz", hash = "sha256:e714210b761febd02a067a1ee00106b6aeb79c0a6dda80b58588b089550f0184", size = 13120 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ca/67/c8b10fa620303affa2240e7f45221a3c103a2e7d4f96dd8b9da4ec6cc84a/resend-2.6.0-py2.py3-none-any.whl", hash = "sha256:53f21fdee6ef18b2c90319b79b38fee048b6d47336283c0a1818bebcaf93c401", size = 19635 },
]
[[package]]
name = "rich"
version = "13.9.4"
@ -2123,7 +2123,7 @@ name = "tqdm"
version = "4.67.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "platform_system == 'Windows'" },
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 }
wheels = [

1564
apps/web/bun.lock Normal file

File diff suppressed because it is too large Load diff

View file

@ -28,9 +28,9 @@ services:
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
# Additional required environment variables
- LEARNHOUSE_CONTACT_EMAIL=${LEARNHOUSE_CONTACT_EMAIL}
- LEARNHOUSE_EMAIL_PROVIDER=${LEARNHOUSE_EMAIL_PROVIDER}
- LEARNHOUSE_IS_AI_ENABLED=${LEARNHOUSE_IS_AI_ENABLED}
- LEARNHOUSE_RESEND_API_KEY=${LEARNHOUSE_RESEND_API_KEY}
- LEARNHOUSE_MAILGUN_API_KEY=${LEARNHOUSE_MAILGUN_API_KEY}
- LEARNHOUSE_MAILGUN_DOMAIN=${LEARNHOUSE_MAILGUN_DOMAIN}
- LEARNHOUSE_SELF_HOSTED=${LEARNHOUSE_SELF_HOSTED}
- LEARNHOUSE_SITE_DESCRIPTION=${LEARNHOUSE_SITE_DESCRIPTION}
- LEARNHOUSE_SITE_NAME=${LEARNHOUSE_SITE_NAME}
@ -38,7 +38,7 @@ services:
- LEARNHOUSE_SYSTEM_EMAIL_ADDRESS=${LEARNHOUSE_SYSTEM_EMAIL_ADDRESS}
# Backend port configuration
- LEARNHOUSE_PORT=9000
# Frontend port configuration
# Frontend port configuration
- PORT=8000
- HOSTNAME=0.0.0.0
depends_on: