Spaces:
Sleeping
Sleeping
Commit ·
6383573
1
Parent(s): 89a1d85
Use manager email API for notifications (multipart/form-data); SMTP fallback
Browse files- README.md +1 -1
- app/notifier.py +61 -11
- requirements.txt +1 -0
README.md
CHANGED
|
@@ -55,7 +55,7 @@ Standalone web application for satellite image change detection with **user acco
|
|
| 55 |
|
| 56 |
- **Database**: set `DATABASE_URL` (e.g. `postgresql://user:pass@host/db`) to use another DB; otherwise SQLite under `data/satellite_app.db` is used.
|
| 57 |
- **JWT**: set `SECRET_KEY` in `app/auth.py` (or via env) in production.
|
| 58 |
-
- **Email**:
|
| 59 |
|
| 60 |
## Project layout
|
| 61 |
|
|
|
|
| 55 |
|
| 56 |
- **Database**: set `DATABASE_URL` (e.g. `postgresql://user:pass@host/db`) to use another DB; otherwise SQLite under `data/satellite_app.db` is used.
|
| 57 |
- **JWT**: set `SECRET_KEY` in `app/auth.py` (or via env) in production.
|
| 58 |
+
- **Email**: By default, notifications are sent via the manager's email API (`https://emailservice.managemybusinessess.com/api/email/send`). Override with `EMAIL_API_URL` if needed. To use SMTP (e.g. Gmail) instead, set `EMAIL_API_URL` to empty and set `SMTP_USER` and `SMTP_PASS`.
|
| 59 |
|
| 60 |
## Project layout
|
| 61 |
|
app/notifier.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
"""
|
| 2 |
Email notification module.
|
| 3 |
-
Sends HTML-formatted detection reports via
|
| 4 |
-
|
| 5 |
-
Credentials
|
| 6 |
"""
|
| 7 |
import logging
|
| 8 |
import os
|
|
@@ -20,6 +20,12 @@ EMAIL_RE = re.compile(r"^[^\s@]+@[^\s@]+\.[^\s@]+$")
|
|
| 20 |
|
| 21 |
TEMPLATE_PATH = Path(__file__).resolve().parent.parent / "templates" / "ChangeDetection.html"
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
def _smtp_settings():
|
| 25 |
return {
|
|
@@ -102,11 +108,46 @@ def build_email_body(
|
|
| 102 |
return html
|
| 103 |
|
| 104 |
|
| 105 |
-
def
|
| 106 |
-
"""Send email via
|
| 107 |
-
if not
|
| 108 |
-
return False, "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
|
|
|
|
|
|
|
|
|
| 110 |
settings = _smtp_settings()
|
| 111 |
smtp_user = settings["user"]
|
| 112 |
smtp_pass = settings["password"]
|
|
@@ -114,7 +155,6 @@ def _send_html_email(recipient: str, subject: str, html_body: str):
|
|
| 114 |
smtp_port = settings["port"]
|
| 115 |
|
| 116 |
if not smtp_user or not smtp_pass:
|
| 117 |
-
logger.warning("SMTP_USER or SMTP_PASS not set — skipping email notification")
|
| 118 |
return False, "SMTP credentials are not configured on the server."
|
| 119 |
|
| 120 |
msg = MIMEMultipart("alternative")
|
|
@@ -131,7 +171,7 @@ def _send_html_email(recipient: str, subject: str, html_body: str):
|
|
| 131 |
server.ehlo()
|
| 132 |
server.login(smtp_user, smtp_pass)
|
| 133 |
server.sendmail(smtp_user, recipient, msg.as_string())
|
| 134 |
-
logger.info("
|
| 135 |
return True, None
|
| 136 |
except smtplib.SMTPAuthenticationError:
|
| 137 |
logger.exception("SMTP authentication failed")
|
|
@@ -146,6 +186,16 @@ def _send_html_email(recipient: str, subject: str, html_body: str):
|
|
| 146 |
return False, f"{type(exc).__name__}: {exc}"
|
| 147 |
|
| 148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
def send_notification(
|
| 150 |
recipient: str,
|
| 151 |
title: str,
|
|
@@ -166,14 +216,14 @@ def send_notification(
|
|
| 166 |
|
| 167 |
|
| 168 |
def send_test_email(recipient: str):
|
| 169 |
-
"""Send a small test email
|
| 170 |
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
|
| 171 |
html_body = f"""
|
| 172 |
<html>
|
| 173 |
<body style="font-family: Arial, sans-serif; color: #222;">
|
| 174 |
<h2>AI Change Detection Email Test</h2>
|
| 175 |
<p>This is a test email from the AI Change Detection application.</p>
|
| 176 |
-
<p>If you received this, the
|
| 177 |
<p><strong>Timestamp:</strong> {now}</p>
|
| 178 |
</body>
|
| 179 |
</html>
|
|
|
|
| 1 |
"""
|
| 2 |
Email notification module.
|
| 3 |
+
Sends HTML-formatted detection reports via the manager's email API (HTTPS)
|
| 4 |
+
or SMTP (e.g. Gmail) when API URL is not set.
|
| 5 |
+
Credentials and API URL from environment variables.
|
| 6 |
"""
|
| 7 |
import logging
|
| 8 |
import os
|
|
|
|
| 20 |
|
| 21 |
TEMPLATE_PATH = Path(__file__).resolve().parent.parent / "templates" / "ChangeDetection.html"
|
| 22 |
|
| 23 |
+
# Manager's email API (multipart/form-data). When set, used instead of SMTP.
|
| 24 |
+
EMAIL_API_URL = os.environ.get(
|
| 25 |
+
"EMAIL_API_URL",
|
| 26 |
+
"https://emailservice.managemybusinessess.com/api/email/send",
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
|
| 30 |
def _smtp_settings():
|
| 31 |
return {
|
|
|
|
| 108 |
return html
|
| 109 |
|
| 110 |
|
| 111 |
+
def _send_via_api(recipient: str, subject: str, html_body: str):
|
| 112 |
+
"""Send email via manager's API (POST multipart/form-data)."""
|
| 113 |
+
if not EMAIL_API_URL or not EMAIL_API_URL.strip():
|
| 114 |
+
return False, "EMAIL_API_URL is not set."
|
| 115 |
+
url = EMAIL_API_URL.strip()
|
| 116 |
+
try:
|
| 117 |
+
import requests
|
| 118 |
+
# API expects multipart/form-data: ToEmail, Subject, Body, FileName, AttachmentBase64
|
| 119 |
+
files = {
|
| 120 |
+
"ToEmail": (None, recipient),
|
| 121 |
+
"Subject": (None, subject),
|
| 122 |
+
"Body": (None, html_body),
|
| 123 |
+
"FileName": (None, ""),
|
| 124 |
+
"AttachmentBase64": (None, ""),
|
| 125 |
+
}
|
| 126 |
+
resp = requests.post(
|
| 127 |
+
url,
|
| 128 |
+
files=files,
|
| 129 |
+
timeout=30,
|
| 130 |
+
headers={"Accept": "*/*"},
|
| 131 |
+
)
|
| 132 |
+
if resp.status_code >= 200 and resp.status_code < 300:
|
| 133 |
+
logger.info("Email API: sent to %s", recipient)
|
| 134 |
+
return True, None
|
| 135 |
+
msg = f"API returned {resp.status_code}"
|
| 136 |
+
try:
|
| 137 |
+
body = resp.text or resp.reason
|
| 138 |
+
if body:
|
| 139 |
+
msg = f"{msg}: {body[:200]}"
|
| 140 |
+
except Exception:
|
| 141 |
+
pass
|
| 142 |
+
logger.warning("Email API failed: %s", msg)
|
| 143 |
+
return False, msg
|
| 144 |
+
except Exception as exc:
|
| 145 |
+
logger.exception("Email API request failed: %s", exc)
|
| 146 |
+
return False, f"{type(exc).__name__}: {exc}"
|
| 147 |
|
| 148 |
+
|
| 149 |
+
def _send_via_smtp(recipient: str, subject: str, html_body: str):
|
| 150 |
+
"""Send email via SMTP (e.g. Gmail)."""
|
| 151 |
settings = _smtp_settings()
|
| 152 |
smtp_user = settings["user"]
|
| 153 |
smtp_pass = settings["password"]
|
|
|
|
| 155 |
smtp_port = settings["port"]
|
| 156 |
|
| 157 |
if not smtp_user or not smtp_pass:
|
|
|
|
| 158 |
return False, "SMTP credentials are not configured on the server."
|
| 159 |
|
| 160 |
msg = MIMEMultipart("alternative")
|
|
|
|
| 171 |
server.ehlo()
|
| 172 |
server.login(smtp_user, smtp_pass)
|
| 173 |
server.sendmail(smtp_user, recipient, msg.as_string())
|
| 174 |
+
logger.info("SMTP email sent to %s", recipient)
|
| 175 |
return True, None
|
| 176 |
except smtplib.SMTPAuthenticationError:
|
| 177 |
logger.exception("SMTP authentication failed")
|
|
|
|
| 186 |
return False, f"{type(exc).__name__}: {exc}"
|
| 187 |
|
| 188 |
|
| 189 |
+
def _send_html_email(recipient: str, subject: str, html_body: str):
|
| 190 |
+
"""Send email: use manager's API if EMAIL_API_URL is set, else SMTP."""
|
| 191 |
+
if not _valid_email(recipient):
|
| 192 |
+
return False, "Enter a valid recipient email address."
|
| 193 |
+
|
| 194 |
+
if EMAIL_API_URL and EMAIL_API_URL.strip():
|
| 195 |
+
return _send_via_api(recipient, subject, html_body)
|
| 196 |
+
return _send_via_smtp(recipient, subject, html_body)
|
| 197 |
+
|
| 198 |
+
|
| 199 |
def send_notification(
|
| 200 |
recipient: str,
|
| 201 |
title: str,
|
|
|
|
| 216 |
|
| 217 |
|
| 218 |
def send_test_email(recipient: str):
|
| 219 |
+
"""Send a small test email to verify delivery."""
|
| 220 |
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
|
| 221 |
html_body = f"""
|
| 222 |
<html>
|
| 223 |
<body style="font-family: Arial, sans-serif; color: #222;">
|
| 224 |
<h2>AI Change Detection Email Test</h2>
|
| 225 |
<p>This is a test email from the AI Change Detection application.</p>
|
| 226 |
+
<p>If you received this, the email configuration is working.</p>
|
| 227 |
<p><strong>Timestamp:</strong> {now}</p>
|
| 228 |
</body>
|
| 229 |
</html>
|
requirements.txt
CHANGED
|
@@ -9,3 +9,4 @@ pillow>=10.0.0
|
|
| 9 |
numpy>=1.24.0
|
| 10 |
opencv-python-headless>=4.8.0
|
| 11 |
scikit-learn>=1.3.0
|
|
|
|
|
|
| 9 |
numpy>=1.24.0
|
| 10 |
opencv-python-headless>=4.8.0
|
| 11 |
scikit-learn>=1.3.0
|
| 12 |
+
requests>=2.28.0
|