import azure.functions as func
import json
import smtplib
import ssl
import base64
import os
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
from email.mime.application import MIMEApplication
import logging
def main(req: func.HttpRequest) -> func.HttpResponse:
"""
Azure Function to send emails with attachments using Office365 SMTP
Expected JSON payload:
{
"to_emails": ["recipient1@domain.com", "recipient2@domain.com"],
"cc_emails": ["cc@domain.com"], # Optional
"bcc_emails": ["bcc@domain.com"], # Optional
"subject": "Email Subject",
"body": "Email body content",
"body_type": "html", # or "plain"
"attachments": [ # Optional
{
"filename": "document.pdf",
"content": "base64_encoded_content",
"content_type": "application/pdf"
}
]
}
"""
logging.info('Email function triggered')
try:
# Get configuration from environment variables
smtp_server = os.environ.get('SMTP_SERVER', 'smtp.office365.com')
smtp_port = int(os.environ.get('SMTP_PORT', '587'))
sender_email = os.environ.get('SENDER_EMAIL')
sender_password = os.environ.get('SENDER_PASSWORD')
if not sender_email or not sender_password:
return func.HttpResponse(
json.dumps({"error": "SENDER_EMAIL and SENDER_PASSWORD must be configured"}),
status_code=400,
mimetype="application/json"
)
# Parse request body
try:
req_body = req.get_json()
except ValueError:
return func.HttpResponse(
json.dumps({"error": "Invalid JSON in request body"}),
status_code=400,
mimetype="application/json"
)
# Validate required fields
required_fields = ['to_emails', 'subject', 'body']
for field in required_fields:
if field not in req_body:
return func.HttpResponse(
json.dumps({"error": f"Missing required field: {field}"}),
status_code=400,
mimetype="application/json"
)
# Extract email details
to_emails = req_body['to_emails']
cc_emails = req_body.get('cc_emails', [])
bcc_emails = req_body.get('bcc_emails', [])
subject = req_body['subject']
body = req_body['body']
body_type = req_body.get('body_type', 'html')
attachments = req_body.get('attachments', [])
# Create message
msg = MIMEMultipart()
msg['From'] = sender_email
msg['To'] = ', '.join(to_emails)
if cc_emails:
msg['Cc'] = ', '.join(cc_emails)
msg['Subject'] = subject
# Add body
if body_type.lower() == 'html':
msg.attach(MIMEText(body, 'html'))
else:
msg.attach(MIMEText(body, 'plain'))
# Add attachments
for attachment in attachments:
try:
filename = attachment['filename']
content = base64.b64decode(attachment['content'])
content_type = attachment.get('content_type', 'application/octet-stream')
# Create attachment
part = MIMEApplication(content, Name=filename)
part['Content-Disposition'] = f'attachment; filename="{filename}"'
msg.attach(part)
logging.info(f'Added attachment: {filename}')
except Exception as e:
logging.error(f'Error adding attachment {attachment.get("filename", "unknown")}: {str(e)}')
return func.HttpResponse(
json.dumps({"error": f"Error processing attachment: {str(e)}"}),
status_code=400,
mimetype="application/json"
)
# Send email
all_recipients = to_emails + cc_emails + bcc_emails
context = ssl.create_default_context()
with smtplib.SMTP(smtp_server, smtp_port) as server:
server.starttls(context=context)
server.login(sender_email, sender_password)
server.send_message(msg, to_addrs=all_recipients)
logging.info(f'Email sent successfully to {len(all_recipients)} recipients')
return func.HttpResponse(
json.dumps({
"status": "success",
"message": f"Email sent successfully to {len(all_recipients)} recipients",
"recipients": all_recipients
}),
status_code=200,
mimetype="application/json"
)
except smtplib.SMTPAuthenticationError as e:
logging.error(f'SMTP Authentication Error: {str(e)}')
return func.HttpResponse(
json.dumps({"error": "Authentication failed. Check email credentials."}),
status_code=401,
mimetype="application/json"
)
except smtplib.SMTPException as e:
logging.error(f'SMTP Error: {str(e)}')
return func.HttpResponse(
json.dumps({"error": f"SMTP error: {str(e)}"}),
status_code=500,
mimetype="application/json"
)
except Exception as e:
logging.error(f'Unexpected error: {str(e)}')
return func.HttpResponse(
json.dumps({"error": f"Unexpected error: {str(e)}"}),
status_code=500,
mimetype="application/json"
)