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

Revert Resend; use SMTP only, custom email API to be integrated later

Browse files
Files changed (3) hide show
  1. README.md +1 -6
  2. app/notifier.py +18 -53
  3. requirements.txt +0 -1
README.md CHANGED
@@ -55,12 +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 (Hugging Face Spaces)**: Outbound SMTP is blocked on Spaces. Use [Resend](https://resend.com) (HTTPS API):
59
- 1. Sign up at [resend.com](https://resend.com), create an API key.
60
- 2. In your Space → **Settings** → **Repository secrets**, add:
61
- - `RESEND_API_KEY` = your Resend API key (e.g. `re_...`).
62
- - (Optional) `RESEND_FROM` = sender string, e.g. `AI Change Detection <onboarding@resend.dev>` or your verified domain.
63
- 3. Emails will be sent via Resend; no SMTP or Gmail app password needed on the server.
64
 
65
  ## Project layout
66
 
 
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
 
app/notifier.py CHANGED
@@ -1,8 +1,8 @@
1
  """
2
  Email notification module.
3
- Sends HTML-formatted emails via Resend HTTPS API (works on HF Spaces)
4
- or fallback to SMTP for local development.
5
- Credentials from environment variables only.
6
  """
7
  import logging
8
  import os
@@ -20,15 +20,6 @@ EMAIL_RE = re.compile(r"^[^\s@]+@[^\s@]+\.[^\s@]+$")
20
 
21
  TEMPLATE_PATH = Path(__file__).resolve().parent.parent / "templates" / "ChangeDetection.html"
22
 
23
- # Resend: preferred on HF Spaces (HTTPS, no outbound SMTP)
24
- RESEND_API_KEY = os.environ.get("RESEND_API_KEY", "")
25
- RESEND_FROM = os.environ.get("RESEND_FROM", "AI Change Detection <onboarding@resend.dev>")
26
-
27
- if RESEND_API_KEY:
28
- logger.info("Email: using Resend HTTPS API (RESEND_API_KEY set)")
29
- else:
30
- logger.info("Email: using SMTP fallback (set RESEND_API_KEY for Hugging Face Spaces)")
31
-
32
 
33
  def _smtp_settings():
34
  return {
@@ -111,32 +102,11 @@ def build_email_body(
111
  return html
112
 
113
 
114
- def _send_via_resend(recipient: str, subject: str, html_body: str):
115
- """Send email using Resend HTTPS API. Works on HF Spaces."""
116
- if not RESEND_API_KEY:
117
- return False, "RESEND_API_KEY is not set."
118
- try:
119
- import resend
120
- resend.api_key = RESEND_API_KEY
121
- r = resend.Emails.send({
122
- "from": RESEND_FROM,
123
- "to": [recipient],
124
- "subject": subject,
125
- "html": html_body,
126
- })
127
- # SDK may return object with .id or dict with "id"
128
- ok = (getattr(r, "id", None) or (isinstance(r, dict) and r.get("id")) or r is not None)
129
- if ok:
130
- logger.info("Resend email sent to %s", recipient)
131
- return True, None
132
- return False, "Resend API returned no id."
133
- except Exception as exc:
134
- logger.exception("Resend send failed: %s", exc)
135
- return False, f"{type(exc).__name__}: {exc}"
136
-
137
 
138
- def _send_via_smtp(recipient: str, subject: str, html_body: str):
139
- """Send email via SMTP (for local dev; often blocked on HF)."""
140
  settings = _smtp_settings()
141
  smtp_user = settings["user"]
142
  smtp_pass = settings["password"]
@@ -144,7 +114,8 @@ def _send_via_smtp(recipient: str, subject: str, html_body: str):
144
  smtp_port = settings["port"]
145
 
146
  if not smtp_user or not smtp_pass:
147
- return False, "SMTP credentials are not configured."
 
148
 
149
  msg = MIMEMultipart("alternative")
150
  msg["Subject"] = subject
@@ -160,27 +131,21 @@ def _send_via_smtp(recipient: str, subject: str, html_body: str):
160
  server.ehlo()
161
  server.login(smtp_user, smtp_pass)
162
  server.sendmail(smtp_user, recipient, msg.as_string())
163
- logger.info("SMTP email sent to %s", recipient)
164
  return True, None
165
  except smtplib.SMTPAuthenticationError:
166
  logger.exception("SMTP authentication failed")
167
  return False, "SMTP authentication failed. Check the Gmail app password."
168
  except Exception as exc:
169
- logger.error("SMTP failed to %s: %s: %s", recipient, type(exc).__name__, exc)
 
 
 
 
 
170
  return False, f"{type(exc).__name__}: {exc}"
171
 
172
 
173
- def _send_html_email(recipient: str, subject: str, html_body: str):
174
- """Send email: use Resend if API key set, else SMTP."""
175
- if not _valid_email(recipient):
176
- return False, "Enter a valid recipient email address."
177
-
178
- if RESEND_API_KEY:
179
- return _send_via_resend(recipient, subject, html_body)
180
- # Fallback to SMTP (e.g. local dev)
181
- return _send_via_smtp(recipient, subject, html_body)
182
-
183
-
184
  def send_notification(
185
  recipient: str,
186
  title: str,
@@ -201,14 +166,14 @@ def send_notification(
201
 
202
 
203
  def send_test_email(recipient: str):
204
- """Send a small test email to verify delivery."""
205
  now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
206
  html_body = f"""
207
  <html>
208
  <body style="font-family: Arial, sans-serif; color: #222;">
209
  <h2>AI Change Detection Email Test</h2>
210
  <p>This is a test email from the AI Change Detection application.</p>
211
- <p>If you received this, the email configuration is working.</p>
212
  <p><strong>Timestamp:</strong> {now}</p>
213
  </body>
214
  </html>
 
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
 
21
  TEMPLATE_PATH = Path(__file__).resolve().parent.parent / "templates" / "ChangeDetection.html"
22
 
 
 
 
 
 
 
 
 
 
23
 
24
  def _smtp_settings():
25
  return {
 
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
  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")
121
  msg["Subject"] = subject
 
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")
138
  return False, "SMTP authentication failed. Check the Gmail app password."
139
  except Exception as exc:
140
+ logger.error(
141
+ "Failed to send notification email to %s: %s: %s",
142
+ recipient,
143
+ type(exc).__name__,
144
+ exc,
145
+ )
146
  return False, f"{type(exc).__name__}: {exc}"
147
 
148
 
 
 
 
 
 
 
 
 
 
 
 
149
  def send_notification(
150
  recipient: str,
151
  title: str,
 
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>
requirements.txt CHANGED
@@ -9,4 +9,3 @@ pillow>=10.0.0
9
  numpy>=1.24.0
10
  opencv-python-headless>=4.8.0
11
  scikit-learn>=1.3.0
12
- resend>=2.0.0
 
9
  numpy>=1.24.0
10
  opencv-python-headless>=4.8.0
11
  scikit-learn>=1.3.0