🔍 Code Extractor

function send_email_ms365

Maturity: 78

Sends an email through Microsoft 365 Graph API with support for HTML content, multiple recipients (to/cc/bcc), and file attachments.

File:
/tf/active/vicechatdev/CDocs/utils/notifications.py
Lines:
446 - 543
Complexity:
moderate

Purpose

This function provides a complete email sending solution using Microsoft 365's Graph API. It handles authentication token retrieval, constructs properly formatted email messages with HTML bodies, manages multiple recipient types (to, cc, bcc), and supports file attachments by encoding them in base64. The function is designed for applications that need to send emails programmatically through Microsoft 365 accounts, with comprehensive error handling and logging.

Source Code

def send_email_ms365(to_addresses: List[str], 
                    subject: str, 
                    body_html: str, 
                    body_text: str = None,
                    cc_addresses: List[str] = None,
                    bcc_addresses: List[str] = None,
                    attachments: List[Dict[str, Any]] = None) -> bool:
    """
    Send email through Microsoft 365 Graph API.
    
    Args:
        to_addresses: List of recipient email addresses
        subject: Email subject
        body_html: HTML body content
        body_text: Optional plain text body
        cc_addresses: Optional list of CC recipients
        bcc_addresses: Optional list of BCC recipients
        attachments: Optional list of attachments as dictionaries with keys:
                    'filename', 'content' (bytes), 'content_type'
                    
    Returns:
        Boolean indicating success
    """
    try:
        # Get token
        token = get_ms365_token()
        if not token:
            logger.error("Failed to get MS365 token")
            return False
            
        # Prepare headers
        headers = {
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        }
        
        # Prepare email data
        email_data = {
            'message': {
                'subject': subject,
                'body': {
                    'contentType': 'HTML',
                    'content': body_html
                },
                'toRecipients': [{'emailAddress': {'address': email}} for email in to_addresses]
            },
            'saveToSentItems': 'true'
        }
        
        # Add CC if provided
        if cc_addresses:
            email_data['message']['ccRecipients'] = [
                {'emailAddress': {'address': email}} for email in cc_addresses
            ]
            
        # Add BCC if provided
        if bcc_addresses:
            email_data['message']['bccRecipients'] = [
                {'emailAddress': {'address': email}} for email in bcc_addresses
            ]
            
        # Add attachments if provided
        if attachments:
            email_data['message']['attachments'] = []
            for attachment in attachments:
                # Convert content to base64
                content_bytes = attachment['content']
                if isinstance(content_bytes, str):
                    content_bytes = content_bytes.encode('utf-8')
                content_b64 = base64.b64encode(content_bytes).decode('utf-8')
                
                attachment_data = {
                    '@odata.type': '#microsoft.graph.fileAttachment',
                    'name': attachment['filename'],
                    'contentType': attachment.get('content_type', 'application/octet-stream'),
                    'contentBytes': content_b64
                }
                email_data['message']['attachments'].append(attachment_data)
        #logger.info("headers and email_data: ", headers, email_data)
        # Send email through MS Graph API
        #if 'wim@vicebio.com' in to_addresses or 'wim.tiest@oneco.be' in to_addresses:
        if True:
            response = requests.post(
                'https://graph.microsoft.com/v1.0/users/' + settings.MS365_SENDER_EMAIL + '/sendMail',
                headers=headers,
                json=email_data
            )
            
            if response.status_code >= 200 and response.status_code < 300:
                return True
            else:
                logger.error(f"MS365 API error: {response.status_code}, {response.text}")
                return False
        return True
            
    except Exception as e:
        logger.error(f"Error sending email through MS365: {e}")
        return False

Parameters

Name Type Default Kind
to_addresses List[str] - positional_or_keyword
subject str - positional_or_keyword
body_html str - positional_or_keyword
body_text str None positional_or_keyword
cc_addresses List[str] None positional_or_keyword
bcc_addresses List[str] None positional_or_keyword
attachments List[Dict[str, Any]] None positional_or_keyword

Parameter Details

to_addresses: List of email addresses for primary recipients. Must be valid email address strings. At least one recipient is required.

subject: The subject line of the email as a string. Can be any text content.

body_html: The main email body content in HTML format. This is the primary content that will be displayed to recipients.

body_text: Optional plain text version of the email body. Currently not used in the implementation but provided for potential fallback scenarios. Defaults to None.

cc_addresses: Optional list of email addresses to be copied (CC) on the email. Must be valid email address strings. Defaults to None.

bcc_addresses: Optional list of email addresses to be blind copied (BCC) on the email. Must be valid email address strings. Defaults to None.

attachments: Optional list of dictionaries, where each dictionary represents a file attachment with keys: 'filename' (str, name of the file), 'content' (bytes or str, file content), and 'content_type' (str, MIME type, defaults to 'application/octet-stream'). Content is automatically base64-encoded. Defaults to None.

Return Value

Type: bool

Returns a boolean value: True if the email was successfully sent (HTTP status code 200-299 from MS Graph API), False if there was any error including token retrieval failure, API errors, or exceptions during processing.

Dependencies

  • requests
  • msal
  • base64
  • logging

Required Imports

import requests
import base64
import logging
from typing import List, Dict, Any

Conditional/Optional Imports

These imports are only needed under specific conditions:

from CDocs.config import settings

Condition: Required for MS365_SENDER_EMAIL configuration setting

Required (conditional)
Custom get_ms365_token() function

Condition: Required for obtaining Microsoft 365 authentication token

Required (conditional)

Usage Example

# Basic email sending
result = send_email_ms365(
    to_addresses=['recipient@example.com'],
    subject='Test Email',
    body_html='<h1>Hello</h1><p>This is a test email.</p>'
)

# Email with CC, BCC, and attachments
with open('report.pdf', 'rb') as f:
    pdf_content = f.read()

attachments = [
    {
        'filename': 'report.pdf',
        'content': pdf_content,
        'content_type': 'application/pdf'
    }
]

result = send_email_ms365(
    to_addresses=['recipient1@example.com', 'recipient2@example.com'],
    subject='Monthly Report',
    body_html='<p>Please find the attached report.</p>',
    cc_addresses=['manager@example.com'],
    bcc_addresses=['archive@example.com'],
    attachments=attachments
)

if result:
    print('Email sent successfully')
else:
    print('Failed to send email')

Best Practices

  • Ensure get_ms365_token() function is properly implemented and returns valid OAuth tokens
  • Validate email addresses before passing them to the function to avoid API errors
  • Handle the boolean return value to implement proper error handling and retry logic
  • Be aware of Microsoft Graph API rate limits when sending bulk emails
  • Ensure attachment content is in bytes format; the function handles string-to-bytes conversion but bytes are preferred
  • The function saves sent emails to the Sent Items folder by default
  • Monitor logger output for detailed error messages when emails fail to send
  • Note that body_text parameter is currently unused; only HTML body is sent
  • The function contains commented-out conditional logic (if True:) that may need review for production use
  • Ensure proper Azure AD app permissions are granted for the Mail.Send scope

Similar Components

AI-powered semantic similarity - components with related functionality:

  • function gen_send_email 75.4% similar

    Sends templated emails using either MS365 or SMTP provider, with support for multiple recipients, attachments, and HTML/text rendering.

    From: /tf/active/vicechatdev/CDocs/utils/notifications.py
  • function send_email_smtp 72.8% similar

    Sends emails via SMTP server with support for HTML/text content, multiple recipients (to/cc/bcc), and file attachments.

    From: /tf/active/vicechatdev/CDocs/utils/notifications.py
  • class O365Client 60.8% similar

    A client class for interacting with Microsoft 365 Graph API to send emails with authentication, validation, and attachment support.

    From: /tf/active/vicechatdev/email-forwarder/src/forwarder/o365_client.py
  • function test_send_email 56.2% similar

    Interactive test function that prompts the user to send a test email through the O365Client to verify email sending functionality.

    From: /tf/active/vicechatdev/email-forwarder/test_service.py
  • function get_ms365_token 53.6% similar

    Acquires an OAuth access token for Microsoft 365 using the MSAL library with client credentials flow for authenticating with Microsoft Graph API.

    From: /tf/active/vicechatdev/CDocs/utils/notifications.py
← Back to Browse