coderuday21 commited on
Commit
6383573
·
1 Parent(s): 89a1d85

Use manager email API for notifications (multipart/form-data); SMTP fallback

Browse files
Files changed (3) hide show
  1. README.md +1 -1
  2. app/notifier.py +61 -11
  3. 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**: Notifications use SMTP (e.g. Gmail). Set `SMTP_USER` and `SMTP_PASS` (Gmail app password) in the environment. On Hugging Face Spaces, outbound SMTP is often blocked; a custom email API will be integrated when available.
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 SMTP (e.g. Gmail).
4
- A custom email API (from your manager) can be integrated later via env/config.
5
- Credentials are read from environment variables.
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 _send_html_email(recipient: str, subject: str, html_body: str):
106
- """Send email via SMTP. Set SMTP_USER and SMTP_PASS (e.g. Gmail app password)."""
107
- if not _valid_email(recipient):
108
- return False, "Enter a valid recipient email address."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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("Notification email sent to %s", recipient)
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 so SMTP configuration can be verified quickly."""
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 SMTP configuration is working.</p>
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