Created
June 29, 2025 08:14
-
-
Save nsdevaraj/b58b2b9f93fd602b9d655268af4492df to your computer and use it in GitHub Desktop.
Teams addon
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from fastapi import FastAPI, Depends, HTTPException, status | |
from fastapi.middleware.cors import CORSMiddleware | |
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials | |
from pydantic import BaseModel, EmailStr | |
from typing import List, Optional | |
import httpx | |
import os | |
from dotenv import load_dotenv | |
import sqlite3 | |
from datetime import datetime | |
import json | |
load_dotenv() | |
app = FastAPI(title="Teams Email Bridge API", version="1.0.0") | |
app.add_middleware( | |
CORSMiddleware, | |
allow_origins=["https://*.teams.microsoft.com", "https://teams.microsoft.com"], | |
allow_credentials=True, | |
allow_methods=["*"], | |
allow_headers=["*"], | |
) | |
security = HTTPBearer() | |
def init_db(): | |
conn = sqlite3.connect('teams_email.db') | |
cursor = conn.cursor() | |
cursor.execute(''' | |
CREATE TABLE IF NOT EXISTS users ( | |
id TEXT PRIMARY KEY, | |
email TEXT NOT NULL, | |
access_token TEXT, | |
refresh_token TEXT, | |
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
) | |
''') | |
cursor.execute(''' | |
CREATE TABLE IF NOT EXISTS email_templates ( | |
id INTEGER PRIMARY KEY AUTOINCREMENT, | |
user_id TEXT, | |
name TEXT NOT NULL, | |
subject TEXT, | |
body TEXT, | |
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
FOREIGN KEY (user_id) REFERENCES users (id) | |
) | |
''') | |
cursor.execute(''' | |
CREATE TABLE IF NOT EXISTS sent_emails ( | |
id INTEGER PRIMARY KEY AUTOINCREMENT, | |
user_id TEXT, | |
recipient TEXT, | |
subject TEXT, | |
sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
teams_context TEXT, | |
FOREIGN KEY (user_id) REFERENCES users (id) | |
) | |
''') | |
conn.commit() | |
conn.close() | |
init_db() | |
# Pydantic models | |
class EmailRequest(BaseModel): | |
to: List[EmailStr] | |
subject: str | |
body: str | |
teams_context: Optional[str] = None | |
template_id: Optional[int] = None | |
class Template(BaseModel): | |
name: str | |
subject: str | |
body: str | |
class TeamsContext(BaseModel): | |
chat_snippet: Optional[str] = None | |
meeting_participants: Optional[List[str]] = None | |
meeting_id: Optional[str] = None | |
# Microsoft Graph API integration | |
class GraphAPIClient: | |
def __init__(self, access_token: str): | |
self.access_token = access_token | |
self.base_url = "https://graph.microsoft.com/v1.0" | |
self.headers = { | |
"Authorization": f"Bearer {access_token}", | |
"Content-Type": "application/json" | |
} | |
async def send_email(self, email_data: EmailRequest): | |
"""Send email via Microsoft Graph API""" | |
message = { | |
"message": { | |
"subject": email_data.subject, | |
"body": { | |
"contentType": "HTML", | |
"content": email_data.body | |
}, | |
"toRecipients": [ | |
{"emailAddress": {"address": recipient}} | |
for recipient in email_data.to | |
] | |
} | |
} | |
async with httpx.AsyncClient() as client: | |
response = await client.post( | |
f"{self.base_url}/me/sendMail", | |
headers=self.headers, | |
json=message | |
) | |
if response.status_code != 202: | |
raise HTTPException( | |
status_code=response.status_code, | |
detail=f"Failed to send email: {response.text}" | |
) | |
return {"status": "sent", "message_id": response.headers.get("request-id")} | |
async def get_contacts(self, limit: int = 50): | |
"""Get user's Outlook contacts""" | |
async with httpx.AsyncClient() as client: | |
response = await client.get( | |
f"{self.base_url}/me/contacts?$top={limit}&$select=displayName,emailAddresses", | |
headers=self.headers | |
) | |
if response.status_code == 200: | |
return response.json()["value"] | |
else: | |
raise HTTPException( | |
status_code=response.status_code, | |
detail="Failed to fetch contacts" | |
) | |
# Authentication helpers | |
async def verify_teams_token(credentials: HTTPAuthorizationCredentials = Depends(security)): | |
"""Verify Microsoft Teams/Graph API token""" | |
token = credentials.credentials | |
async with httpx.AsyncClient() as client: | |
response = await client.get( | |
"https://graph.microsoft.com/v1.0/me", | |
headers={"Authorization": f"Bearer {token}"} | |
) | |
if response.status_code != 200: | |
raise HTTPException( | |
status_code=status.HTTP_401_UNAUTHORIZED, | |
detail="Invalid or expired token" | |
) | |
user_info = response.json() | |
return { | |
"user_id": user_info["id"], | |
"email": user_info["userPrincipalName"], | |
"token": token | |
} | |
def get_db_connection(): | |
return sqlite3.connect('teams_email.db') | |
# API Routes | |
@app.get("/") | |
async def root(): | |
return {"message": "Teams Email Bridge API", "version": "1.0.0"} | |
@app.get("/health") | |
async def health_check(): | |
return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()} | |
@app.post("/api/v1/send-email") | |
async def send_email( | |
email_request: EmailRequest, | |
current_user = Depends(verify_teams_token) | |
): | |
"""Send email with optional Teams context""" | |
try: | |
graph_client = GraphAPIClient(current_user["token"]) | |
result = await graph_client.send_email(email_request) | |
# Log sent email | |
conn = get_db_connection() | |
cursor = conn.cursor() | |
cursor.execute( | |
"INSERT INTO sent_emails (user_id, recipient, subject, teams_context) VALUES (?, ?, ?, ?)", | |
( | |
current_user["user_id"], | |
", ".join(email_request.to), | |
email_request.subject, | |
email_request.teams_context | |
) | |
) | |
conn.commit() | |
conn.close() | |
return result | |
except Exception as e: | |
raise HTTPException(status_code=500, detail=str(e)) | |
@app.get("/api/v1/contacts") | |
async def get_contacts( | |
limit: int = 50, | |
current_user = Depends(verify_teams_token) | |
): | |
"""Get user's Outlook contacts""" | |
graph_client = GraphAPIClient(current_user["token"]) | |
return await graph_client.get_contacts(limit) | |
@app.get("/api/v1/templates") | |
async def get_templates(current_user = Depends(verify_teams_token)): | |
"""Get user's email templates""" | |
conn = get_db_connection() | |
cursor = conn.cursor() | |
cursor.execute( | |
"SELECT id, name, subject, body FROM email_templates WHERE user_id = ?", | |
(current_user["user_id"],) | |
) | |
templates = [] | |
for row in cursor.fetchall(): | |
templates.append({ | |
"id": row[0], | |
"name": row[1], | |
"subject": row[2], | |
"body": row[3] | |
}) | |
conn.close() | |
return {"templates": templates} | |
@app.post("/api/v1/templates") | |
async def create_template( | |
template: Template, | |
current_user = Depends(verify_teams_token) | |
): | |
"""Create new email template""" | |
conn = get_db_connection() | |
cursor = conn.cursor() | |
cursor.execute( | |
"INSERT INTO email_templates (user_id, name, subject, body) VALUES (?, ?, ?, ?)", | |
(current_user["user_id"], template.name, template.subject, template.body) | |
) | |
template_id = cursor.lastrowid | |
conn.commit() | |
conn.close() | |
return {"id": template_id, "message": "Template created successfully"} | |
@app.get("/api/v1/sent-emails") | |
async def get_sent_emails( | |
limit: int = 20, | |
current_user = Depends(verify_teams_token) | |
): | |
"""Get user's sent email history""" | |
conn = get_db_connection() | |
cursor = conn.cursor() | |
cursor.execute( | |
"SELECT recipient, subject, sent_at, teams_context FROM sent_emails WHERE user_id = ? ORDER BY sent_at DESC LIMIT ?", | |
(current_user["user_id"], limit) | |
) | |
emails = [] | |
for row in cursor.fetchall(): | |
emails.append({ | |
"recipient": row[0], | |
"subject": row[1], | |
"sent_at": row[2], | |
"teams_context": row[3] | |
}) | |
conn.close() | |
return {"sent_emails": emails} | |
@app.post("/api/v1/teams-context") | |
async def process_teams_context( | |
context: TeamsContext, | |
current_user = Depends(verify_teams_token) | |
): | |
"""Process and format Teams context for email""" | |
formatted_context = "" | |
if context.chat_snippet: | |
formatted_context += f"From our Teams discussion:\n{context.chat_snippet}\n\n" | |
if context.meeting_participants: | |
participants = ", ".join(context.meeting_participants) | |
formatted_context += f"Meeting participants: {participants}\n\n" | |
return { | |
"formatted_context": formatted_context, | |
"suggested_subject": "Follow-up from our Teams discussion" | |
} | |
# Default email templates | |
DEFAULT_TEMPLATES = [ | |
{ | |
"name": "Meeting Follow-up", | |
"subject": "Follow-up from our Teams meeting", | |
"body": "Hi,<br><br>Thanks for the productive discussion in our Teams meeting. Here are the key points and next steps:<br><br>[ACTION_ITEMS]<br><br>Please let me know if I missed anything or if you have questions.<br><br>Best regards" | |
}, | |
{ | |
"name": "Quick Introduction", | |
"subject": "Introduction from Teams", | |
"body": "Hi,<br><br>Following up from our Teams discussion, I wanted to introduce you to [NAME] who can help with [TOPIC].<br><br>[CONTEXT]<br><br>Looking forward to connecting you both.<br><br>Best regards" | |
}, | |
{ | |
"name": "Action Items", | |
"subject": "Action items from our Teams discussion", | |
"body": "Hi,<br><br>Here are the action items we discussed:<br><br>• [ITEM_1]<br>• [ITEM_2]<br>• [ITEM_3]<br><br>Timeline: [DEADLINE]<br><br>Let me know if you need any clarification.<br><br>Best regards" | |
} | |
] | |
@app.post("/api/v1/setup-defaults") | |
async def setup_default_templates(current_user = Depends(verify_teams_token)): | |
"""Set up default templates for new users""" | |
conn = get_db_connection() | |
cursor = conn.cursor() | |
for template in DEFAULT_TEMPLATES: | |
cursor.execute( | |
"INSERT OR IGNORE INTO email_templates (user_id, name, subject, body) VALUES (?, ?, ?, ?)", | |
(current_user["user_id"], template["name"], template["subject"], template["body"]) | |
) | |
conn.commit() | |
conn.close() | |
return {"message": "Default templates created successfully"} | |
if __name__ == "__main__": | |
import uvicorn | |
uvicorn.run(app, host="0.0.0.0", port=8000) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fastapi==0.104.1 | |
uvicorn==0.24.0 | |
python-multipart==0.0.6 | |
pydantic==2.5.0 | |
sqlalchemy==2.0.23 | |
databases==0.8.0 | |
aiosqlite==0.19.0 | |
httpx==0.25.2 | |
python-jose[cryptography]==3.3.0 | |
python-dotenv==1.0.0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
┌────────────────────────────────────┐ | |
│ Teams Email Bridge [?] [Settings]│ | |
├─────────────────────────────────────┤ | |
│ 📧 Quick Compose │ | |
│ 📋 Templates │ | |
│ 👥 Recent Contacts │ | |
│ 📝 Sent History │ | |
└─────────────────────────────────────┘ | |
┌─────────────────────────────────────┐ | |
│ 📧 Send Email [X]│ | |
├─────────────────────────────────────┤ | |
│ To: [Contact Search/Auto-complete] │ | |
│ Subject: [Smart suggestions] │ | |
├─────────────────────────────────────┤ | |
│ ✨ Include Teams Context │ | |
│ ☐ Chat snippet ☐ Participants │ | |
├─────────────────────────────────────┤ | |
│ [Rich Text Editor] │ | |
│ │ | |
│ [Template ▼] [Send Later ▼] │ | |
│ │ | |
│ [Cancel] [Send Email] │ | |
└─────────────────────────────────────┘ | |
┌─────────────────────────────────────┐ | |
│ Choose Template [X]│ | |
├─────────────────────────────────────┤ | |
│ 🔄 Meeting Follow-up │ | |
│ 👋 Quick Introduction │ | |
│ ✅ Action Items │ | |
│ ➕ Create Custom Template │ | |
├─────────────────────────────────────┤ | |
│ Preview: │ | |
│ [Template preview with variables] │ | |
│ │ | |
│ [Cancel] [Use Template] │ | |
└─────────────────────────────────────┘ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment