How to send emails using PHPMailer and Office 365 SMTP

On November 04, 2024
18min read
Veljko Ristić Content Manager @ Mailtrap
Hennadii Alforov Technical Lead @GoDaddy
This image is a comical graphic representation of PHPMailer and Office 365 for an article that covers the topic in detail.

Since you’re reading this, I’m pretty sure you went to the official Microsoft docs and realized the PHPMailer Office 365 setup isn’t as easy as you thought. 

No worries, we worked out the complexities and I’ll hold your hand every step of the way. In this article, I cover the following:

PHPMailer setup and configuration

I’ll cover the PHPMailer setup and configuration from scratch. Also, there’s a separate section dedicated to OAuth2 to authenticate the SMTP connection. 

Step 1: Installing PHPMailer

I’m using the standard command to install PHPMailer via Composer. It pulls the latest version of PHPMailer (at the time of writing it was 6.9.1). 

composer require phpmailer/phpmailer

Step 2: PHPMailer configuration for Office 365

Here’s the basic setup for Office 365. Note that this is just for shows and the stepped approach. Soon, I’ll update the code with OAuth2, which is critical if you want to send via the outgoing SMTP server. 

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require 'vendor/autoload.php';

$mail = new PHPMailer(true);

try {
    //Server settings
    $mail->isSMTP();
    $mail->Host       = 'smtp.office365.com';
    $mail->SMTPAuth   = true;
    $mail->SMTPSecure = 'tls';
    $mail->Port       = 587;

    //OAuth2 authentication setup goes here (covered below)
   
    //Sender and recipient settings
    $mail->setFrom('youremail@yourdomain.com', 'Your Name');
    $mail->addAddress('recipient@domain.com', 'Recipient Name');

    //Content
    $mail->isHTML(true);
    $mail->Subject = 'Test Email';
    $mail->Body    = 'This is a test email using Office 365 SMTP and PHPMailer.';
   
    //Send email
    $mail->send();
    echo 'Message has been sent';
} catch (Exception $e) {
    echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}

Step 3: OAuth authentication

Microsoft is deprecating basic authentication for security reasons. The OAuth2 is now the recommended method for authenticating with Office 365. 

It takes quite a few steps and is more complicated compared to the basic flow, so I’ll break it down for you into more manageable chunks.

Step 3.1: Use Azure AD to register an application

  1. Log into the Azure portal and go to Azure Active Directory (Microsoft Entra ID)
  2. Under App registrations, click New Registration.
  3. Give your app a name (e.g., “PHPMailer OAuth2”).
  4. Leave “Supported account types” by default
  5. Set the Redirect URI type to “Public client/native” and provide a redirect URI (e.g., http://localhost).
  6. Register the app.
  7. Get the Client ID and Tenant ID on the “Overview” page
  8. Under API permissions, add permissions for “APIs my organization uses” and find Office 365 Exchange Online
    1. Choose “Application permissions,” select “SMTP.SendAsApp” and click “Add permissions”
    2. After press the button “Grant admin consent for {your_user_name}” and confirm
  9. Optional. Create a new Client Secret on the “Certificates & secrets” page (provide a description and expiry date)

Important: Save the Client ID, Tenant ID, and Client Secret (optional) for later use. 

Step 3.2: Enable OAuth2 with PHPMailer

First, install the necessary package using the command below.

composer require greew/oauth2-azure-provider

Next, I’ll update the PHPMailer configuration to reflect the changes. 

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\OAuth;
//@see https://github.com/greew/oauth2-azure-provider
use Greew\OAuth2\Client\Provider\Azure;

require 'vendor/autoload.php';

$email = getenv('AZURE_OFFICE365_EMAIL'); // your office365 email
$clientId = getenv('AZURE_CLIENT_ID'); // Azure Client ID
$tenantId = getenv('AZURE_TENANT_ID'); // Azure Tenant ID
$clientSecret = getenv('AZURE_CLIENT_SECRET_VALUE'); // Optional. Azure Client Secret Value (Certificates & secrets)
$refreshToken = getenv('AZURE_REFRESH_TOKEN');

$mail = new PHPMailer(true);

try {
   // Configure PHPMailer for SMTP
   $mail->isSMTP();
   $mail->Host       = 'smtp.office365.com';
   $mail->SMTPAuth   = true;
   $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
   $mail->Port       = 587;

   // Set OAuth2 token
   $mail->AuthType = 'XOAUTH2';
   $mail->setOAuth(new OAuth([
       'provider' => new Azure([
           'clientId' => $clientId,
           'tenantId' => $tenantId,
           'clientSecret' => $clientSecret
       ]),
       'clientId' => $clientId,
       'refreshToken' => $refreshToken,
       'clientSecret' => $clientSecret,
       'userName' => $email,
   ]));

   //Sender and recipient settings
   $mail->setFrom($email, 'First Last'); // your office365 email
   $mail->addAddress('someone@someserver.com', 'John Doe');

   //Content
   $mail->isHTML(true);
   $mail->Subject = 'OAuth2 Email Test';
   $mail->Body    = 'This is a test email using OAuth2 authentication with Office 365 SMTP and PHPMailer.';

   //Send email
   $mail->send();
   echo 'Message has been sent';
} catch (Exception $e) {
   echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}

Quick breakdown:

  • OAuth2 setup: I use the Greew\OAuth2\Client\Provider\Azure package to handle OAuth2 authentication.
  • Refresh Token: ses to authenticate the SMTP connection.
  • Security tip: Never hard-code credentials directly into your code—use environment variables instead for security.

Important: The code above contains placeholders AZURE_CLIENT_ID, AZURE_CLIENT_SECRET_VALUE, AZURE_TENANT_ID, etc. Make sure to replace these with your actual variables. 

Get an OAuth2 token from the provider

To send emails, you first need an OAuth token. The PHPMailer library already has a default file for this purpose, which you can run on your local server to get the required ‘refreshToken.’ 

Hit the link for reference, and here’s a quick setup tutorial. 

Setup:

  • Install this script on your server so that it’s accessible as[https/http]://<yourdomain>/index.php
  • Ensure dependencies are installed with composer install.  
  • Set the script address as the app’s redirect URL (http://localhost)
  • If no refresh token is obtained when running this file, revoke access to your app and run the script again.
  • Open the URL where you installed the script, fill out the form, choose “Azure” as the provider, and click ‘Continue.’ 

If you don’t want to use the built-in form, set your Client ID, tenant ID, secret, and provider inside the file and run it using the console.

As a result, if everything is entered correctly, you should see a message like this: “Refresh Token: …YOUR_REFRESH_TOKEN…..” Save this refresh token somewhere; we will need it in the future.

Quick breakdown:

  • OAuth2 tokens are retrieved using the authorization_code flow.
  • The getAccessToken('authorization_code') method retrieves an access and refresh token that allows PHPMailer to authenticate with Office 365.

Send email using Office 365 SMTP

To send emails using Office 365, you need to configure PHPMailer with Office 365’s SMTP settings. Below I included the SMTP server details and a simple example to send a plain-text email.

Of course, I’m assuming that you’re done with OAuth2 and Azure AD. Otherwise, the described methodology won’t work. 

SMTP server details:

  • SMTP host: smtp.office365.com
  • Port: 587 (TLS)
  • SMTP authentication: Yes
  • Encryption: TLS
  • Username: Your full Office 365 email address
  • Password: The app-specific password or access token for OAuth2

Here’s the script to send a plain text message. 

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\OAuth;
//@see https://github.com/greew/oauth2-azure-provider
use Greew\OAuth2\Client\Provider\Azure;

require 'vendor/autoload.php';

$email = getenv('AZURE_OFFICE365_EMAIL'); // your office365 email
$clientId = getenv('AZURE_CLIENT_ID'); // Azure Client ID
$tenantId = getenv('AZURE_TENANT_ID'); // Azure Tenant ID
$clientSecret = getenv('AZURE_CLIENT_SECRET_VALUE'); // Optional. Azure Client Secret Value (Certificates & secrets)
$refreshToken = getenv('AZURE_REFRESH_TOKEN');

$mail = new PHPMailer(true);

try {
   // Configure PHPMailer for SMTP
   $mail->isSMTP();
   $mail->Host       = 'smtp.office365.com';
   $mail->SMTPAuth   = true;
   $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
   $mail->Port       = 587;

   // Set OAuth2 token
   $mail->AuthType = 'XOAUTH2';
   $mail->setOAuth(new OAuth([
       'provider' => new Azure([
           'clientId' => $clientId,
           'tenantId' => $tenantId,
           'clientSecret' => $clientSecret
       ]),
       'clientId' => $clientId,
       'refreshToken' => $refreshToken,
       'clientSecret' => $clientSecret,
       'userName' => $email,
   ]));

   //Sender and recipient settings
   $mail->setFrom($email, 'First Last'); // your office365 email
   $mail->addAddress('someone@someserver.com', 'Recipient Name');


//    $mail->addAddress('someone@someserver.com', 'John Doe');

   //Content
   $mail->isHTML(true);
   $mail->Subject = 'OAuth2 Email Test';
   $mail->Body    = 'This is a test email using OAuth2 authentication with Office 365 SMTP and PHPMailer.';

   //Send email
   $mail->send();
   echo 'Message has been sent';
} catch (Exception $e) {
   echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}

Quick breakdown:

  • I used the Azure provider from the greew/oauth2-azure-provider library to handle OAuth2 for Office 365. You need to provide your Client ID, Client Secret (optional), and Tenant ID from your Azure app registration.
  • The AuthType = 'XOAUTH2' and the setOAuth() method configure PHPMailer to use OAuth2 for authentication instead of a username and password.
  • The settings remain the same as Office 365 requires: smtp.office365.com, port 587, and tls encryption.
  • To check for invalid token errors, ensure your Azure app permissions include the SMTP.SendAsApp scope.
  • Azure xoauth2 example from the PHPMailer library

Send email to multiple recipients

Here’s an example of how to send an email to multiple recipients. I’ll include CC and BCC recipients to showcase how it’s done.

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\OAuth;
//@see https://github.com/greew/oauth2-azure-provider
use Greew\OAuth2\Client\Provider\Azure;

require 'vendor/autoload.php';

$email = getenv('AZURE_OFFICE365_EMAIL'); // your office365 email
$clientId = getenv('AZURE_CLIENT_ID'); // Azure Client ID
$tenantId = getenv('AZURE_TENANT_ID'); // Azure Tenant ID
$clientSecret = getenv('AZURE_CLIENT_SECRET_VALUE'); // Optional. Azure Client Secret Value (Certificates & secrets)
$refreshToken = getenv('AZURE_REFRESH_TOKEN');

$mail = new PHPMailer(true);

try {
   // Configure PHPMailer for SMTP
   $mail->isSMTP();
   $mail->Host       = 'smtp.office365.com';
   $mail->SMTPAuth   = true;
   $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
   $mail->Port       = 587;

   // Set OAuth2 token
   $mail->AuthType = 'XOAUTH2';
   $mail->setOAuth(new OAuth([
       'provider' => new Azure([
           'clientId' => $clientId,
           'tenantId' => $tenantId,
           'clientSecret' => $clientSecret
       ]),
       'clientId' => $clientId,
       'refreshToken' => $refreshToken,
       'clientSecret' => $clientSecret,
       'userName' => $email,
   ]));

   //Sender and recipient settings
   $mail->setFrom($email, 'First Last'); // your office365 email

   $mail->addAddress('recipient1@domain.com', 'Recipient One'); // Main recipient
   $mail->addCC('recipient2@domain.com', 'Recipient Two'); // CC recipient
   $mail->addBCC('recipient3@domain.com', 'Recipient Three'); // BCC recipient

   //Content
   $mail->isHTML(false);  // Send plain-text email
   $mail->Subject = 'Test Email with Multiple Recipients';
   $mail->Body    = 'This is a test email sent to multiple recipients using OAuth2 authentication with Office 365 SMTP and PHPMailer.';

   //Send email
   $mail->send();
   echo 'Message has been sent';
} catch (Exception $e) {
   echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}

Quick breakdown:

You can add as many recipients as needed using the addAddress() method for the main recipient, addCC() for Carbon Copy (CC), and addBCC() for Blind Carbon Copy (BCC). 

Even so, note that there are specific Office throughput limits. Anyway, the logic of adding multiple recipients in the example is as follows:

Send email with attachments

Check the snippet to send emails with attachments. 

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\OAuth;
//@see https://github.com/greew/oauth2-azure-provider
use Greew\OAuth2\Client\Provider\Azure;

require 'vendor/autoload.php';

$email = getenv('AZURE_OFFICE365_EMAIL'); // your office365 email
$clientId = getenv('AZURE_CLIENT_ID'); // Azure Client ID
$tenantId = getenv('AZURE_TENANT_ID'); // Azure Tenant ID
$clientSecret = getenv('AZURE_CLIENT_SECRET_VALUE'); // Optional. Azure Client Secret Value (Certificates & secrets)
$refreshToken = getenv('AZURE_REFRESH_TOKEN');

$mail = new PHPMailer(true);

try {
   // Configure PHPMailer for SMTP
   $mail->isSMTP();
   $mail->Host       = 'smtp.office365.com';
   $mail->SMTPAuth   = true;
   $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
   $mail->Port       = 587;

   // Set OAuth2 token
   $mail->AuthType = 'XOAUTH2';
   $mail->setOAuth(new OAuth([
       'provider' => new Azure([
           'clientId' => $clientId,
           'tenantId' => $tenantId,
           'clientSecret' => $clientSecret
       ]),
       'clientId' => $clientId,
       'refreshToken' => $refreshToken,
       'clientSecret' => $clientSecret,
       'userName' => $email,
   ]));

   // Sender and recipient settings
   $mail->setFrom($email, 'Your Name'); // your office365 email
   $mail->addAddress('recipient@domain.com', 'Recipient Name');

   // Add an attachment
   $mail->addAttachment('/path/to/office365_attachment.jpg', 'office365_attachment.jpg');  // Replace with your file path and name

   // Email content
   $mail->isHTML(false);  // Send plain-text email
   $mail->Subject = 'Test Email with Attachment';
   $mail->Body    = 'This is a test email with an attachment sent using OAuth2 authentication with Office 365 SMTP and PHPMailer.';

   //Send email
   $mail->send();
   echo 'Message has been sent';
} catch (Exception $e) {
   echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}

Quick breakdown:

I’ll focus only on the attachments section since I already covered other functions. 

  • To attach a file, I use the addAttachment() method. The first parameter is the file path, and the second is the name that appears in the email. For instance: addAttachment('/path/to/office365_attachment.jpg', 'office365_attachment.jpg'); attaches the file office365_attachment.jpg.

Common errors:

  • Large attachment error: If your attachments exceed the 25 MB limit, the email won’t be sent. You can reduce the file size or switch to a link-based method.
  • Attachment not found: If the file path is incorrect or inaccessible, PHPMailer will throw an error. Ensure the file path is valid and accessible to your script.

Send HTML email

Here’s how to send an HTML email. 

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\OAuth;
//@see https://github.com/greew/oauth2-azure-provider
use Greew\OAuth2\Client\Provider\Azure;

require 'vendor/autoload.php';

$email = getenv('AZURE_OFFICE365_EMAIL'); // your office365 email
$clientId = getenv('AZURE_CLIENT_ID'); // Azure Client ID
$tenantId = getenv('AZURE_TENANT_ID'); // Azure Tenant ID
$clientSecret = getenv('AZURE_CLIENT_SECRET_VALUE'); // Optional. Azure Client Secret Value (Certificates & secrets)
$refreshToken = getenv('AZURE_REFRESH_TOKEN');

$mail = new PHPMailer(true);

try {
   // Configure PHPMailer for SMTP
   $mail->isSMTP();
   $mail->Host       = 'smtp.office365.com';
   $mail->SMTPAuth   = true;
   $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
   $mail->Port       = 587;

   // Set OAuth2 token
   $mail->AuthType = 'XOAUTH2';
   $mail->setOAuth(new OAuth([
       'provider' => new Azure([
           'clientId' => $clientId,
           'tenantId' => $tenantId,
           'clientSecret' => $clientSecret
       ]),
       'clientId' => $clientId,
       'refreshToken' => $refreshToken,
       'clientSecret' => $clientSecret,
       'userName' => $email,
   ]));

   // Sender and recipient settings
   $mail->setFrom($email, 'Your Name'); // your office365 email
   $mail->addAddress('recipient@domain.com', 'Recipient Name');

   // HTML email content
   $mail->isHTML(true);  // Enable HTML mode
   $mail->Subject = 'Test HTML Email';
   $mail->Body    = '<h1>Welcome to Our Service!</h1><p>This is a <b>HTML</b> email sent using PHPMailer and OAuth2 authentication with Office 365 SMTP.</p>';
   $mail->AltBody = 'This is the plain-text version of the HTML email.';

   //Send email
   $mail->send();
   echo 'Message has been sent';
} catch (Exception $e) {
   echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}

Quick breakdown:

  • I use the isHTML(true) method to enable HTML mode in the email.
  • The Body contains the HTML content. In the example above, I included a simple <h1> heading and a <p> paragraph with some bold text.
  • The AltBody is a plain-text fallback version of the email. This is necessary in case the recipient’s email client doesn’t support HTML emails, and it improves overall accessibility.

If you need a more detailed tutorial, check the one in this link

Send email with an embedded image

Important note: Plain text emails don’t exactly support embedded images the same way HTML emails do. They lack the correct formatting capabilities like the <img> tag, which may affect the deliverability and accessibility of the email. 

Long story short, I’ll be using HTML email since it’s much more reliable. 

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\OAuth;
//@see https://github.com/greew/oauth2-azure-provider
use Greew\OAuth2\Client\Provider\Azure;

require 'vendor/autoload.php';

$email = getenv('AZURE_OFFICE365_EMAIL'); // your office365 email
$clientId = getenv('AZURE_CLIENT_ID'); // Azure Client ID
$tenantId = getenv('AZURE_TENANT_ID'); // Azure Tenant ID
$clientSecret = getenv('AZURE_CLIENT_SECRET_VALUE'); // Optional. Azure Client Secret Value (Certificates & secrets)
$refreshToken = getenv('AZURE_REFRESH_TOKEN');

$mail = new PHPMailer(true);

try {
   // Configure PHPMailer for SMTP
   $mail->isSMTP();
   $mail->Host       = 'smtp.office365.com';
   $mail->SMTPAuth   = true;
   $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
   $mail->Port       = 587;

   // Set OAuth2 token
   $mail->AuthType = 'XOAUTH2';
   $mail->setOAuth(new OAuth([
       'provider' => new Azure([
           'clientId' => $clientId,
           'tenantId' => $tenantId,
           'clientSecret' => $clientSecret
       ]),
       'clientId' => $clientId,
       'refreshToken' => $refreshToken,
       'clientSecret' => $clientSecret,
       'userName' => $email,
   ]));

   // Sender and recipient settings
   $mail->setFrom($email, 'Your Name'); // your office365 email
   $mail->addAddress('recipient@domain.com', 'Recipient Name');

   // Embedding the image
   $mail->addEmbeddedImage('/path/to/image.jpg', 'image_cid');  // Replace with your image file path

   // HTML email content with the embedded image
   $mail->isHTML(true);  // Enable HTML mode
   $mail->Subject = 'Test Email with Embedded Image';
   $mail->Body    = '<h1>Hello!</h1><p>This is an email with an embedded image:</p><img src="cid:image_cid">'; // Reference the image using 'cid'
   $mail->AltBody = 'This is the plain-text version of the email with an embedded image.';

   //Send email
   $mail->send();
   echo 'Message has been sent';
} catch (Exception $e) {
   echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}

Quick breakdown:

  • I use the addEmbeddedImage() method to embed an image within the email.
  • The first parameter is the file path to the image (‘/path/to/image.jpg’), and the second is the Content-ID (CID) that you’ll use to reference the image within the HTML (image_cid in this case).
  • To display the embedded image in the email, use the cid:image_cid format in the src attribute of the <img> tag. For example; <img src="cid:image_cid">. This tells the email client to use the image with the corresponding CID.

Common errors:

  • The missing image error: If the image doesn’t appear, make sure the cid value in the <img> tag matches exactly with the CID you’ve assigned in addEmbeddedImage().
  • The file not found error: If the file path is incorrect, PHPMailer will throw an error. Ensure the image path is valid and accessible to the script.

Send email using Outlook SMTP

Outlook SMTP works similarly to Office 365, but there are a few important differences in the server settings. Of course, Outlook SMTP is free and mostly for personal use, whereas Office 365 SMTP is geared towards business users. 

I’ll be covering the technical server specs, an exemplary code snippet, the main technical differences, and some troubleshooting tips.  

Here we go.

Server-specific technical details:

  • SMTP host: smtp-mail.outlook.com
  • Port: 587 (TLS)
  • SMTP authentication: Yes
  • Encryption: tls
  • Username: Your full Outlook.com email address
  • Password: The app-specific password or access token for OAuth2

Note: I’ll keep using OAuth2 since it’s the most reliable method, but basic authentication may still work with Outlook. 

Code example:

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\OAuth;
//@see https://github.com/greew/oauth2-azure-provider
use Greew\OAuth2\Client\Provider\Azure;

require 'vendor/autoload.php';

$email = getenv('AZURE_OFFICE365_EMAIL'); // your office365 email
$clientId = getenv('AZURE_CLIENT_ID'); // Azure Client ID
$tenantId = getenv('AZURE_TENANT_ID'); // Azure Tenant ID
$clientSecret = getenv('AZURE_CLIENT_SECRET_VALUE'); // Optional. Azure Client Secret Value (Certificates & secrets)
$refreshToken = getenv('AZURE_REFRESH_TOKEN');

$mail = new PHPMailer(true);

try {
   // Configure PHPMailer for SMTP
   $mail->isSMTP();
   $mail->Host       = 'smtp-mail.outlook.com'; // instead of smtp.office365.com
   $mail->SMTPAuth   = true;
   $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
   $mail->Port       = 587;

   // Set OAuth2 token
   $mail->AuthType = 'XOAUTH2';
   $mail->setOAuth(new OAuth([
       'provider' => new Azure([
           'clientId' => $clientId,
           'tenantId' => $tenantId,
           'clientSecret' => $clientSecret
       ]),
       'clientId' => $clientId,
       'refreshToken' => $refreshToken,
       'clientSecret' => $clientSecret,
       'userName' => $email,
   ]));

   // Sender and recipient settings
   $mail->setFrom('your-email@outlook.com', 'Your Name');
   $mail->addAddress('recipient@domain.com', 'Recipient Name');

   // Plain-text email content
   $mail->isHTML(false);  // Disable HTML mode for plain-text
   $mail->Subject = 'Test Email Using Outlook SMTP';
   $mail->Body    = "This is a plain-text email sent using Outlook SMTP and OAuth2 authentication.";

   // Send email
   $mail->send();
   echo 'Message has been sent successfully' . PHP_EOL;
} catch (Exception $e) {
   echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}" . PHP_EOL;
}

Key technical differences between Office 365 and Outlook

SMTP hosts:

  • Outlook: smtp-mail.outlook.com 
  • Office 365: smtp.office365.com

OAuth2 scopes:

Troubleshooting tips and tricks

  1. Authentication errors:
  • Ensure that your Client ID, Client Secret, and Tenant ID are correctly set up in your Azure AD app registration.
  • Verify that your Azure app has permission to access the SMTP.SendAsApp scope.
  1. Connection issues and timeouts:
  • Ensure your server has network access to Outlook’s SMTP server.
  • Use the correct host (smtp-mail.outlook.com) and port (587) with TLS encryption.
  1. OAuth token errors:
  • If you can’t obtain the OAuth2 token, inspect the app registration in Azure and the associated permissions.

Tip: If your email fails to reach the recipient, double-check the address validity and ensure it’s not picked up by spam filters. Also, keep a close eye on rate-limiting; personal Outlook accounts have a heavier rate limit than Office 365.

Microsoft 365 SMTP limitations

Microsoft imposes quite a few, sometimes confusing, limitations. So, I’ll break them down into three categories (technical limits, sending and rate limits, and authentication and security limits). This way it should be easier to decide whether Office 365 is the right choice for you. 

Technical limits

  • There are three different options for sending emails: SMTP Auth client submission, direct send, and SMTP relay. 
  • This article covers SMTP Auth client submission, which is the most common sending scenario (for internal and external addresses). However, the naming can be misleading because SMTP Auth, which is basic authentication, is likely to be blocked for new users. So, as shown in the article, I recommend going the OAuth2 route. 
  • ‘Direct send’ relays emails from a device to internal email addresses. It uses MX records as endpoints and doesn’t require authentication. To stress, it only works for internal recipients, but it could be an okay choice if you need to send emails, let’s say, from your printer or similar devices. 
  • SMTP relay requires a connector and a TLS certificate or a static IP. It’s tricky to set up and can be expensive. Plus, if you choose this option, your emails will be subjected to Microsoft’s spam filters. However, it supports internal and external email sending. 
  • The SPF (Sender Policy Framework) record is critical to ensure smooth sending via Microsoft. This goes regardless of the method you choose, so make sure to set it up properly and review it regularly to avoid getting your emails flagged as spam. 

Sending and rate limits

Daily sending limit

  • For Enterprise accounts (Office 365 Business), the daily sending limit is 10,000 recipients. 
  • The daily limit for Personal and Small Business accounts is typically much lower, around 300 recipients per day.
  • You can include a maximum of 500 recipients in a single email message, which includes recipients in the To, CC, and BCC fields combined.

Rate limit

  • Microsoft 365 applies rate limits if your email sending exceeds the throughput limits set based on your plan. The most common scenario is sending too many emails within a short period. If you hit the limit, your emails may be temporarily throttled or even rejected. 
  • [To the above] The exact period and the number of emails aren’t clearly defined by Microsoft, and it likely includes several factors. To figure it out, I suggest using common sense. For instance, if you typically send 1K emails per hour, then you suddenly spike to 5K or 10K, the sending is likely to be throttled. 

Attachments limits 

  • The maximum attachment size is 25 MB per email. This includes the total size of all attached files plus any encoding overhead. Exceeding the limit, results in failed sending.
  • [Interesting] Microsoft offers a super generous maximum of 250 attachments per message (as long as they don’t exceed 25 MB) 😀 I haven’t tested this, but it would be a fun experiment to see how deliverable this email would be. 

Tip:  For those who send large volumes of emails, 100K a month or higher, it’s better to batch your messages or use an email service designed for bulk sending (such as Mailtrap or another SMTP service). The same goes if you need to handle large attachments. 

Security limitations: Multi-Factor Authentication (MFA) and phasing out legacy authentication

MFA requirement: 

  • Microsoft recommends enabling MFA. It adds an extra layer of protection by requiring a second form of authentication (e.g., a code sent to your phone) in addition to your password.
  • In my experience, it’s more of a necessity than a recommendation as it smoothens out the sending authentication and verification. And, in turn, it somewhat futureproofs your sending setup. 

Basic authentication deprecated:

  • As of October 2022, basic authentication has been largely deprecated for most Microsoft 365 services. If you’re still using just the username and password to authenticate without OAuth2 and MFA, now is the high time to make the switch. 

Is there an alternative to Microsoft SMTP?

Of course, there’s an alternative — for me, that alternative is always Mailtrap Email API/SMTP. It’s part of the Mailtrap Email Delivery Platform and allows you to test, send, and control your emails. 

Okay, okay, I know it sounds like a blatant promo, but give me a moment to explain. 

I’ll cover the reasons to choose Mailtrap based on Office 365 limitations, then give you an exemplary PHPMailer snippet. And if you want to see the service in action right away, hit the play button below. 

Here are the reasons:

  1. Mailtrap SMTP seamlessly integrates with PHPMailer, allowing you to send emails without the complexities of configuring OAuth2 authentication. 
  1. If safety is the top priority (and it should be), Mailtrap supports TLS and SSL encryption. 
  1. You get access to advanced email analytics. This includes deliverability and open rates, click rates, bounce tracking, etc. Everything is packed into a helicopter-view dashboard so you can check the health of your campaigns at a glance. Plus, you’re one click away from drill-down reports for a more detailed overview. 
  1. With SMTP, you also get access to RESTful APIs and SDKs (PHP included). This allows for greater integration flexibility, especially if you want to scale the email infrastructure of your app or website. 
  1. Mailtrap features a visual drag-and-drop email template builder. It also allows you to quickly switch to HTML editing if need be. Of course, you can store, manage, and reuse the templates as you deem fit. 
  1. You get access to 24/7 human support. At Mailtrap, there’s a team of experts to address your questions and requests. Additionally, we have an in-house email deliverability expert who can, for example, assist you in transitioning to Mailtrap even if your infrastructure needs to support millions of emails a month. 
  1. You get access to Mailtrap Email Testing, which is a sandbox solution I’ll explain in detail in the following section. 

Now, I’ll give you an exemplary snippet to send emails via PHPMailer, using Mailtrap SMTP. 

Notes

  • I assume you already added and authenticated your domain with the service. 
  • The exemplary snippet features live.smtp.mailtrap.io endpoint for transactional (user-triggered) emails. There’s also bulk.smtp.mailtrap.io endpoint for bulk messages. You can use both under the same plan. 
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require 'vendor/autoload.php';

$mail = new PHPMailer(true);

try {
    // SMTP configuration for Mailtrap
    $mail->isSMTP();
    $mail->Host       = 'live.smtp.mailtrap.io';
    $mail->SMTPAuth   = true;
    $mail->Username   = 'YOUR_MAILTRAP_USERNAME'; // Replace with your Mailtrap username
    $mail->Password   = 'YOUR_MAILTRAP_PASSWORD'; // Replace with your Mailtrap password
    $mail->SMTPSecure = 'tls'; // Use 'tls' or 'ssl' as required
    $mail->Port       = 587;  // Mailtrap SMTP port

    // Sender and recipient settings
    $mail->setFrom('your-email@example.com', 'Your Name');
    $mail->addAddress('recipient@example.com', 'Recipient Name');

    // Email content
    $mail->isHTML(true);
    $mail->Subject = 'Test Email via Mailtrap SMTP';
    $mail->Body    = '<h1>Hello!</h1><p>This is a test email sent using <b>Mailtrap SMTP</b> with PHPMailer.</p>';
    $mail->AltBody = 'This is the plain-text version of the email content';

    // Send email
    $mail->send();
    echo 'Message has been sent successfully via Mailtrap SMTP';
} catch (Exception $e) {
    echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}

Test emails and email sending on staging

Also part of the Mailtrap Email Delivery Platform, Mailtrap Email Testing provides a safe sandbox to test emails without the risk of sending them to your recipients. 

It helps devs and QA ensure their emails work as intended without affecting production environments. If you use API, you can seamlessly transition from testing to production once you’re happy with your templates. 

Note: Mailtrap Email Testing is available starting from the free plan (test 100 emails a month). 

I’ll cover the key features in greater detail soon. Again, you can hit the play button to see the PHPMailer testing in action and check the exemplary code snippet. 

Exemplary code snippet

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require 'vendor/autoload.php';

$mail = new PHPMailer(true);

try {
    // SMTP configuration for Mailtrap (Testing/Sandbox)
    $mail->isSMTP();
    $mail->Host       = 'sandbox.smtp.mailtrap.io';  // Mailtrap SMTP server
    $mail->SMTPAuth   = true;
    $mail->Username   = 'YOUR_MAILTRAP_USERNAME';  // Mailtrap credentials
    $mail->Password   = 'YOUR_MAILTRAP_PASSWORD';
    $mail->SMTPSecure = 'tls';
    $mail->Port       = 2525;  // Mailtrap's standard port

    // Sender and recipient settings
    $mail->setFrom('your-email@example.com', 'Your Name');
    $mail->addAddress('recipient@example.com', 'Recipient Name');

    // Email content
    $mail->isHTML(true);
    $mail->Subject = 'Test Email in Sandbox with Mailtrap';
    $mail->Body    = '<h1>This is a test email</h1><p>Testing with <b>Mailtrap</b> in a sandbox environment.</p>';
    $mail->AltBody = 'This is a plain-text version of the email content';

    // Send email
    $mail->send();
    echo 'Test email sent successfully via Mailtrap';
} catch (Exception $e) {
    echo "Email could not be sent. Mailer Error: {$mail->ErrorInfo}";
}

Key features:

  • Email preview: Mailtrap allows you to see how your email will look across different email devices, ensuring consistency in the design and format. 
Mailtrap Email Testing HTML preview menu
  • Spam score check: Analyze your emails and get a spam score based on SpamAssassin parameters. You can check whether your email appears in any of the popular blacklists and get quick explanations of the parameters that could be improved. 
Mailtrap email testing Spam Report
  • HTML check: See the support for your email template across different email clients, including web, desktop, and mobile versions. The HTML elements that affect the score are also referenced, and you can jump to the exact lines of code by clicking the number next to “Found on lines:.” 
Mailtrap Email Testing HTML Check menu

In addition to the above, you also get the following:

  • API for QA automation
  • Ready to use integrations in 20+ different languages
  • Multiple inboxes for different projects and stages
  • User management, SSO

Additional resources:

Wrapping up

I’ve walked through the complete process of PHPMailer Office 365 setup, including troubleshooting common issues and securing your email system with OAuth2. With these insights, you’re well-equipped to avoid or resolve common sending limitations like rate limits or authentication errors.

Even so, I hope you’ll give Mailtrap Email API/SMTP a try. If for no other reason then since the setup is simpler and you get advanced analytics 

Article by Veljko Ristić Content Manager @ Mailtrap

Linguist by trade, digital marketer at heart, I’m a Content Manager who’s been in the online space for 10+ years. From ads to e-books, I’ve covered it all as a writer, editor, project manager, and everything in between. Now, my passion is with email infrastructure with a strong focus on technical content and the cutting-edge in programming logic and flows. But I still like spreading my gospels while blogging purely about marketing.

Article by Hennadii Alforov Technical Lead @GoDaddy

Software developer with 10+ years of experience in web development. I have a first-class degree in computer science and a passion for programming and software safety. My primary area of expertise is planning and building PHP&JS applications of different complexity.