This application tests the rate limits of Microsoft Graph API's sendMail endpoint using app-only authentication with client secret credentials.
- Test Microsoft Graph API rate limits (10,000 requests per 10 minutes = 1,000 requests/minute per mailbox)
- Support up to 5 sender mailboxes for concurrent distributed testing
- Send emails with random subjects and bodies to a configurable recipient
- Track success rates, failures, and rate limit responses
- Concurrent sending: Each mailbox sends independently with parallel requests for high-volume testing
- Scalable architecture: Automatically adjusts between sequential (low-rate) and batch concurrent (high-rate) modes
- App-Only Authentication: Uses MSAL with client credentials flow
- High-Volume Concurrent Mode: Uses parallel requests (up to 4 concurrent per Graph API limits)
- Adaptive Architecture: Switches between sequential (low-rate) and batch concurrent (high-rate) modes
- Graph API Compliant: Respects 4 concurrent request limit per application
- Connection Pooling: Optimized HTTP connection management for sustained throughput
- Precise Timing Control: Compensates for API request duration to maintain accurate send rates
- GUI Interface: Easy-to-use graphical interface with rate presets (30, 100, 500, 1000/min)
- API Request Logging: Detailed logging of all Graph API requests and responses to
graph_api_log.txt - Configurable Testing: Adjust emails per minute (per mailbox), test duration, and number of mailboxes
- Real-time Monitoring: Live progress updates and aggregate statistics
- Configuration Persistence: Save and load test configurations
- Comprehensive Logging: Detailed logging of all operations
You need to register an application in Azure AD with the following setup:
- Go to Azure Portal → Azure Active Directory → App registrations
- Click "New registration"
- Set a name (e.g., "Mail Rate Limit Tester")
- Set "Supported account types" to "Accounts in this organizational directory only"
- Click "Register"
After registration, configure the following:
- Go to "API permissions"
- Click "Add a permission" → "Microsoft Graph" → "Application permissions"
- Add the following permission:
- Mail.Send (to send mail as any user)
- Click "Grant admin consent" (requires admin privileges)
- Go to "Certificates & secrets"
- Click "New client secret"
- Add a description and set expiration
- Copy the secret value immediately (you won't be able to see it again)
Note the following from the app registration overview:
- Application (client) ID
- Directory (tenant) ID
Ensure you have valid user mailboxes in your tenant that will be used as senders. The app will send emails on behalf of these users using the Mail.Send application permission.
- Install Python dependencies:
pip install -r requirements.txt- Run the application:
python main.py-
Fill in the configuration fields:
- Tenant ID: Your Azure AD tenant ID
- Client ID: Application (client) ID from app registration
- Client Secret: The secret value you created
- Recipient Email: Email address that will receive test messages
- Sender Mailboxes: Enter 1-5 email addresses (one per line) that will send the messages
-
Configure test parameters:
- Target Emails/Minute (per mailbox): Set your desired rate (default: 30)
- Quick Presets: Use preset buttons for common rates:
- 30/min: Exchange Online typical limit
- 100/min: Moderate volume testing
- 500/min: High volume testing
- 1000/min: Graph API maximum (10k per 10 minutes)
- Test Duration (minutes): How long to run the test
-
Click "Save Config" to save your configuration for future use
The app saves configuration to mail_tester_config.json. Example:
{
"tenant_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"client_secret": "your-secret-here",
"recipient_email": "testrecipient@yourdomain.com",
"sender_mailboxes": [
"sender1@yourdomain.com",
"sender2@yourdomain.com"
],
"emails_per_minute": 30,
"test_duration_minutes": 5
}- Start the application
- Ensure configuration is complete
- Click "Start Test" button
- Review the confirmation dialog showing aggregate targets
- Monitor real-time progress in the log window
- View statistics: Sent, Failed, Rate Limited, Actual Rate
- Click "View API Log" to see detailed Graph API requests/responses
- Click "Stop Test" to abort early if needed
The application logs all Graph API requests and responses to graph_api_log.txt in the same directory. This includes:
- Full request URL, headers, and body
- Response status, headers, and body
- Request duration in seconds
- Timestamps for each call
Click the "View API Log" button in the GUI to open this file in your default text editor during or after a test run.
The application tracks:
- Sent: Successfully sent emails (HTTP 202)
- Failed: Errors other than rate limiting
- Rate Limited: Emails that received HTTP 429 (rate limit)
- Actual Rate: Measured emails/minute achieved
According to Microsoft Graph API limits:
- Graph API concurrency limit: 4 concurrent requests per application (critical throttling limit)
- Graph API rate limit: 10,000 requests per 10 minutes per mailbox = 1,000 requests/minute maximum
- Exchange Online sending limit: Approximately 30 messages/minute per mailbox (separate from Graph API)
- Using multiple sender mailboxes allows concurrent sending for aggregate throughput
- With 5 mailboxes at 4 concurrent requests: Can sustain high throughput while respecting API limits
- Each mailbox operates independently with its own rate limiting
- Rate limiting (HTTP 429) will occur if:
- More than 4 requests are made concurrently
- More than 1,000 requests/minute per mailbox
- More than 10,000 requests per 10-minute window per mailbox
- The
Retry-Afterheader indicates when to retry
The application automatically selects the optimal sending mode:
Low-Rate Mode (≤100 emails/min per mailbox):
- Sequential sending with precise timing
- One request at a time per mailbox
- Optimized for accuracy over throughput
High-Volume Mode (>100 emails/min per mailbox):
- Batch concurrent sending
- Up to 4 parallel requests per mailbox (Graph API limit)
- Connection pooling for optimal performance
- Burst sending with timed delays between batches
- Can sustain 200-400+ emails/minute per mailbox while respecting API limits
- Never commit
mail_tester_config.jsonto version control - Store secrets securely (e.g., Azure Key Vault in production)
- Use least-privilege permissions (only Mail.Send)
- Rotate secrets regularly
Microsoft Graph and Exchange Online have multiple rate limits:
- Per-mailbox limits: 30 messages/minute per sender
- Per-app limits: May vary based on tenant size
- Throttling: May occur under heavy load
- Ensure sender mailboxes exist and are active
- The app requires Mail.Send application permission (app-only context)
- No user interaction is required (daemon/service scenario)
- Verify tenant ID, client ID, and client secret are correct
- Ensure admin consent was granted for Mail.Send permission
- Check that the app registration is in the correct tenant
- Verify sender mailbox addresses are valid
- Ensure Mail.Send application permission is granted
- Check that mailboxes are not disabled or restricted
- Review error messages in the log window
- This is expected behavior when testing limits
- Note the actual rate achieved before hitting limits
- Use multiple sender mailboxes to distribute load
- Authentication: MSAL with ConfidentialClientApplication (client credentials flow)
- Concurrency: asyncio.gather() for parallel task execution across all mailboxes
- HTTP Client: aiohttp for async HTTP requests
- Thread-Safe Stats: asyncio.Lock for coordinating statistics across concurrent tasks
- API: Microsoft Graph v1.0
/users/{id}/sendMailendpoint - GUI: tkinter with threading for async operations
- Logging: Python logging module with GUI and console output
Multi-Level Concurrency Architecture:
- Mailbox-level concurrency: Each sender mailbox runs as an independent async task
- Request-level concurrency: For high rates (>100/min), each mailbox sends multiple requests in parallel
- All mailbox tasks execute concurrently using
asyncio.gather() - Connection pooling: Up to 100 connections per host for sustained high throughput
- Adaptive batching: Automatically calculates optimal batch size based on target rate
- Statistics are updated in a thread-safe manner using async locks
Example with 5 mailboxes at 1000/min each:
Mailbox 1 ──→ [50 concurrent requests × 20 batches/min] ─┐
Mailbox 2 ──→ [50 concurrent requests × 20 batches/min] ─┤
Mailbox 3 ──→ [50 concurrent requests × 20 batches/min] ─┼──→ ~5,000 emails/min
Mailbox 4 ──→ [50 concurrent requests × 20 batches/min] ─┤
Mailbox 5 ──→ [50 concurrent requests × 20 batches/min] ─┘
The application uses several techniques to achieve accurate send rates:
Low-Rate Mode (<100/min):
- High-precision timing: Uses
time.perf_counter()for sub-millisecond accuracy - Duration compensation: Measures actual API request time and schedules next send accordingly
- Pre-scheduling: Calculates exact send time before each request
- No cumulative drift: Each send is scheduled independently
High-Rate Mode (>100/min):
- Batch timing: Sends bursts of parallel requests with controlled delays
- Semaphore control: Limits concurrent requests to prevent overwhelming the API
- Adaptive pacing: Adjusts batch intervals based on actual batch completion time
- Connection reuse: HTTP connection pooling eliminates connection overhead
This is a testing tool. Use responsibly and in accordance with your organization's policies.