123 lines
4.1 KiB
Python
123 lines
4.1 KiB
Python
"""BAN YARO — Outreach E-Mail (Admin)"""
|
|
|
|
import os
|
|
import smtplib
|
|
import ssl
|
|
from email.mime.text import MIMEText
|
|
from email.mime.multipart import MIMEMultipart
|
|
from email.utils import formataddr
|
|
from datetime import datetime
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel
|
|
from typing import List, Optional
|
|
|
|
from auth import require_admin
|
|
from database import db
|
|
|
|
router = APIRouter()
|
|
|
|
_SMTP_HOST = os.getenv("SMTP_HOST", "")
|
|
_SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
|
|
_SMTP_USER = os.getenv("SMTP_USER", "")
|
|
_SMTP_PASS = os.getenv("SMTP_PASS", "")
|
|
_SMTP_FROM = os.getenv("SMTP_FROM", "partner@banyaro.app")
|
|
|
|
TEMPLATES = {
|
|
"influencer_de": {
|
|
"label": "Influencer-Ansprache (DE)",
|
|
"subject": "Ban Yaro — 100 Gründer-Plätze, einer davon für deine Community",
|
|
"body": """Hallo {name},
|
|
|
|
ich bin René und habe Ban Yaro gebaut — eine Hunde-App für Tagebuch, Gesundheit, Giftköder-Alarm und Community. Kostenlos, ohne App Store, direkt als PWA.
|
|
|
|
Ich kontaktiere gerade einige Influencer aus der deutschen Hunde-Community mit einem konkreten Angebot:
|
|
|
|
Was deine Follower bekommen: Wer sich mit deinem persönlichen Code registriert, sichert sich einen der 100 Gründer-Plätze — eine nummerierte Badge ("Gründer #42") die dauerhaft im Profil sichtbar ist. Diese 100 Plätze gibt es genau einmal.
|
|
|
|
Was du bekommst: Partner-Badge in der App, eigener Code, öffentliches Ranking wer die meisten Gründer bringt.
|
|
|
|
Kein Geld, kein verpflichtender Post — aber eine echte Exklusivität die du deiner Community geben kannst.
|
|
|
|
Alle Infos: https://banyaro.app/partner
|
|
|
|
Wenn dich das interessiert, antworte einfach kurz — ich richte deinen Code binnen 24h ein.
|
|
|
|
Viele Grüße,
|
|
René
|
|
banyaro.app""",
|
|
},
|
|
}
|
|
|
|
|
|
class SendRequest(BaseModel):
|
|
to: List[str]
|
|
subject: str
|
|
body: str
|
|
template_name: Optional[str] = None
|
|
|
|
|
|
def _send_smtp(to: str, subject: str, body: str):
|
|
if not _SMTP_HOST or not _SMTP_USER:
|
|
raise RuntimeError("SMTP nicht konfiguriert.")
|
|
msg = MIMEMultipart("alternative")
|
|
msg["Subject"] = subject
|
|
msg["From"] = formataddr(("Ban Yaro Partner", _SMTP_FROM))
|
|
msg["To"] = to
|
|
msg["Reply-To"] = _SMTP_FROM
|
|
msg.attach(MIMEText(body, "plain", "utf-8"))
|
|
ctx = ssl.create_default_context()
|
|
with smtplib.SMTP(_SMTP_HOST, _SMTP_PORT, timeout=15) as s:
|
|
s.ehlo()
|
|
s.starttls(context=ctx)
|
|
s.login(_SMTP_USER, _SMTP_PASS)
|
|
s.sendmail(_SMTP_FROM, to, msg.as_bytes())
|
|
|
|
|
|
@router.get("/templates")
|
|
def list_templates(user=Depends(require_admin)):
|
|
return [{"id": k, "label": v["label"], "subject": v["subject"], "body": v["body"]}
|
|
for k, v in TEMPLATES.items()]
|
|
|
|
|
|
@router.post("/send")
|
|
def send_outreach(data: SendRequest, user=Depends(require_admin)):
|
|
if not data.to:
|
|
raise HTTPException(400, "Mindestens eine Empfänger-Adresse angeben.")
|
|
if not data.subject.strip() or not data.body.strip():
|
|
raise HTTPException(400, "Betreff und Text dürfen nicht leer sein.")
|
|
|
|
sent, failed = [], []
|
|
for addr in data.to:
|
|
addr = addr.strip()
|
|
if not addr:
|
|
continue
|
|
try:
|
|
_send_smtp(addr, data.subject, data.body)
|
|
sent.append(addr)
|
|
# Log in DB
|
|
with db() as conn:
|
|
conn.execute(
|
|
"""INSERT INTO outreach_log
|
|
(sent_by, recipient, subject, body, sent_at)
|
|
VALUES (?, ?, ?, ?, ?)""",
|
|
(user["id"], addr, data.subject, data.body,
|
|
datetime.utcnow().isoformat())
|
|
)
|
|
except Exception as e:
|
|
failed.append({"addr": addr, "error": str(e)})
|
|
|
|
return {"sent": sent, "failed": failed}
|
|
|
|
|
|
@router.get("/log")
|
|
def outreach_log(user=Depends(require_admin)):
|
|
with db() as conn:
|
|
rows = conn.execute(
|
|
"""SELECT ol.id, ol.recipient, ol.subject, ol.sent_at,
|
|
u.name AS sent_by_name
|
|
FROM outreach_log ol
|
|
JOIN users u ON u.id = ol.sent_by
|
|
ORDER BY ol.sent_at DESC LIMIT 100"""
|
|
).fetchall()
|
|
return [dict(r) for r in rows]
|