Download backend/app/gmail_invite.py from Seth0330/EMAILOUT: direct link, hf CLI and curl.
- Browser
- Download file 2.43 kB
-
https://huggingface.co/spaces/Seth0330/EMAILOUT/resolve/main/backend/app/gmail_invite.py
- Command line
-
hf download hf://spaces/Seth0330/EMAILOUT/backend/app/gmail_invite.py
-
curl -L -o gmail_invite.py https://huggingface.co/spaces/Seth0330/EMAILOUT/resolve/main/backend/app/gmail_invite.py
2.43 kB
| """Send messages via Gmail API using a stored OAuth refresh token (gmail.send scope).""" | |
| from __future__ import annotations | |
| import base64 | |
| import os | |
| from email.message import EmailMessage | |
| import httpx | |
| GOOGLE_TOKEN_URI = "https://oauth2.googleapis.com/token" | |
| GMAIL_SEND_URL = "https://gmail.googleapis.com/gmail/v1/users/me/messages/send" | |
| async def refresh_access_token(refresh_token: str) -> str: | |
| client_id = os.environ.get("GOOGLE_CLIENT_ID", "").strip() | |
| client_secret = os.environ.get("GOOGLE_CLIENT_SECRET", "").strip() | |
| if not client_id or not client_secret: | |
| raise ValueError("Google OAuth is not configured") | |
| async with httpx.AsyncClient() as client: | |
| r = await client.post( | |
| GOOGLE_TOKEN_URI, | |
| data={ | |
| "client_id": client_id, | |
| "client_secret": client_secret, | |
| "refresh_token": refresh_token, | |
| "grant_type": "refresh_token", | |
| }, | |
| headers={"Content-Type": "application/x-www-form-urlencoded"}, | |
| timeout=30.0, | |
| ) | |
| if r.status_code != 200: | |
| raise ValueError(f"Token refresh failed ({r.status_code}): {r.text[:500]}") | |
| data = r.json() | |
| at = data.get("access_token") | |
| if not at: | |
| raise ValueError("No access_token in refresh response") | |
| return str(at) | |
| def _rfc822_raw(sender: str, to: str, subject: str, body: str) -> str: | |
| msg = EmailMessage() | |
| msg["Subject"] = subject | |
| msg["From"] = sender | |
| msg["To"] = to | |
| msg.set_content(body) | |
| return base64.urlsafe_b64encode(msg.as_bytes()).decode() | |
| async def send_invite_email_via_gmail( | |
| refresh_token: str, | |
| from_email: str, | |
| to_email: str, | |
| subject: str, | |
| body: str, | |
| ) -> None: | |
| """Raises ValueError on configuration or API errors.""" | |
| if not from_email or "@" not in from_email: | |
| raise ValueError("Inviter has no email address for Gmail send") | |
| access = await refresh_access_token(refresh_token) | |
| raw = _rfc822_raw(from_email, to_email, subject, body) | |
| async with httpx.AsyncClient() as client: | |
| r = await client.post( | |
| GMAIL_SEND_URL, | |
| headers={"Authorization": f"Bearer {access}"}, | |
| json={"raw": raw}, | |
| timeout=30.0, | |
| ) | |
| if r.status_code not in (200, 201): | |
| err = r.text[:1500] if r.text else "" | |
| raise ValueError(f"Gmail send failed ({r.status_code}): {err}") | |