Add email proxy implementation

This commit is contained in:
Dominik Milacher 2025-06-14 11:38:56 +02:00
parent 4b1a4115d8
commit 26ec7ace9e
9 changed files with 182 additions and 0 deletions

1
email-proxy/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
hotels.db

10
email-proxy/Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "proxy:app", "--host", "0.0.0.0", "--port", "8000"]

13
email-proxy/README Normal file
View File

@ -0,0 +1,13 @@
INITIAL SETUP
cd email-proxy
touch hotels.db # IMPORTANT
docker compose up -d --build
In the current directory, to update the image&container run
docker compose up --build
this does not change the volume (db)
For resetting everything, also the db, before run
Use docker compose down -v

View File

@ -0,0 +1,11 @@
services:
email-proxy:
restart: unless-stopped
build: .
ports:
- "8000:8000"
volumes:
- ./hotels.db:/app/hotels.db
environment:
- BREVO_API_KEY=<KEY>
- BREVO_SENDER=<proxy@example.com>

40
email-proxy/helper.py Normal file
View File

@ -0,0 +1,40 @@
# helper.py
from sqlmodel import SQLModel, Session, create_engine, select
from proxy import Hotel, DATABASE_URL
engine = create_engine(DATABASE_URL, echo=False)
def add_hotel(hotel_id, email):
with Session(engine) as session:
existing = session.exec(select(Hotel).where(Hotel.hotel_id == hotel_id)).first()
if existing:
print(f"Hotel '{hotel_id}' already exists with email: {existing.email}")
return
hotel = Hotel(hotel_id=hotel_id, email=email)
session.add(hotel)
session.commit()
print(f"Added: {hotel_id} -> {email}")
def remove_hotel(hotel_id):
with Session(engine) as session:
hotel = session.exec(select(Hotel).where(Hotel.hotel_id == hotel_id)).first()
if not hotel:
print(f"Hotel '{hotel_id}' not found.")
return
session.delete(hotel)
session.commit()
print(f"Removed: {hotel_id}")
if __name__ == "__main__":
import sys
if len(sys.argv) < 3:
print("Usage: python helper.py add <hotel_id> <email>")
print(" or: python helper.py remove <hotel_id>")
exit(1)
command = sys.argv[1].lower()
if command == "add" and len(sys.argv) == 4:
add_hotel(sys.argv[2], sys.argv[3])
elif command == "remove" and len(sys.argv) == 3:
remove_hotel(sys.argv[2])
else:
print("Invalid command or arguments.")

77
email-proxy/proxy.py Normal file
View File

@ -0,0 +1,77 @@
from fastapi import FastAPI, Request, HTTPException
from sqlmodel import Field, SQLModel, Session, create_engine, select
from typing import Optional
import os
import httpx
# --- Database Models ---
class Hotel(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
hotel_id: str = Field(index=True, unique=True)
email: str
# --- App Setup ---
DATABASE_URL = "sqlite:////app/hotels.db"
engine = create_engine(DATABASE_URL, echo=False)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
app = FastAPI()
@app.on_event("startup")
def on_startup():
create_db_and_tables()
# --- API Endpoints ---
@app.post("/api/contact")
async def contact(request: Request):
data = await request.json()
hotel_id = data.get("hotel")
name = data.get("name")
sender_email = data.get("email")
subject = data.get("subject")
message = data.get("message")
if not hotel_id or not message or not sender_email or not subject:
raise HTTPException(status_code=400, detail="Missing required fields.")
# Lookup hotel
with Session(engine) as session:
hotel = session.exec(select(Hotel).where(Hotel.hotel_id == hotel_id)).first()
if not hotel:
raise HTTPException(status_code=404, detail="Unknown hotel.")
await send_brevo_email(
to_email=hotel.email,
from_name=name,
reply_to_email=sender_email,
subject=subject,
message=message,
)
return {"ok": True}
async def send_brevo_email(to_email, from_name, reply_to_email, subject, message):
api_key = os.getenv("BREVO_API_KEY")
sender_email = os.getenv("BREVO_SENDER")
payload = {
"sender": {"name": from_name, "email": sender_email},
"replyTo": {"name": from_name, "email": reply_to_email},
"to": [{"email": to_email}],
"subject": subject,
"textContent": message,
}
async with httpx.AsyncClient() as client:
r = await client.post(
"https://api.brevo.com/v3/smtp/email",
headers={"api-key": api_key, "accept": "application/json", "Content-Type": "application/json"},
json=payload,
timeout=10,
)
if r.status_code >= 400:
raise HTTPException(status_code=500, detail=f"Email failed: {r.text}")

View File

@ -0,0 +1,4 @@
fastapi
uvicorn
sqlmodel
httpx

26
email-proxy/update.py Normal file
View File

@ -0,0 +1,26 @@
# update.py
import sys
import subprocess
if len(sys.argv) < 3:
print("Usage:")
print(" python update.py add <hotel_id> <email>")
print(" python update.py remove <hotel_id>")
sys.exit(1)
command = sys.argv[1].lower()
if command == "add" and len(sys.argv) == 4:
# Run helper.py inside container with add
subprocess.run([
"docker-compose", "run", "--rm", "email-proxy",
"python", "helper.py", "add", sys.argv[2], sys.argv[3]
])
elif command == "remove" and len(sys.argv) == 3:
# Run helper.py inside container with remove
subprocess.run([
"docker-compose", "run", "--rm", "email-proxy",
"python", "helper.py", "remove", sys.argv[2]
])
else:
print("Invalid arguments.")
sys.exit(1)