How To Read Emails Using Microsoft Graph API In Laravel 10?

Last Updated On - February 7th, 2024 Published On - Oct 05, 2023

In today’s fast-paced digital world, effective communication is key. For many, Outlook emails are a primary means of exchanging crucial information. But what if you could automate the process of reading and organizing these emails? Imagine effortlessly searching for specific receipts and converting them into PDFs with Laravel. In this blog post, I’ll take you through a step-by-step guide on how to read, filter, and save emails in PDF using Microsoft Graph API.

Introduction

Managing Outlook emails manually can be time-consuming and prone to errors. With the power of Laravel and Microsoft Graph API, you can automate this process, making it efficient and error-free. Our project will not only read your emails but also filter them to find receipts and save them in user-specific folders in PDF format.

Prerequisites

Before we dive into the code, you need to ensure you have the following prerequisites:

  • A Laravel project set up
  • Access to the Microsoft Graph API
  • Knowledge of OAuth and API authentication

Don’t worry if you don’t fulfill the prerequisites, I’ll explain each part. If you qualify for prerequisites then you can jump to the next section i.e. Redirect to Microsoft Login

Now I am going to explain each prerequisite point along with the code snippets for your Laravel project. Let’s go through them one by one.


Also Read: Sending messages to Microsoft Teams Channel by using Laravel


A Laravel Project Set-Up

Before you start working on your Outlook email processing project, you need to have a Laravel project set up. If you don’t have one, you can create a new Laravel project using Composer:

composer create-project laravel/laravel emailReader

Make sure to navigate to your project directory:

cd eamilReader

Access to the Microsoft Graph API

To access the Microsoft Graph API, you’ll need to create an Azure Active Directory App and configure it. Here’s how you can set up an Azure AD App:

  1. Register Your App: Go to the Azure Portal and register a new application.
  2. Configure Authentication:
    • Under “Authentication,” add the redirect URI where users will be redirected after login. This should match your Laravel project’s URL. (I’ve set it to http://localhost:8000/auth/microsoft/callback)
    • Configure the required permissions. For Outlook email access, you’ll typically need openid, profile, offline_access, Mail.Read and User.Read permissions.
  3. Generate Client ID and Secret:
    • Under “Overview,” you’ll find your Application (client) ID. Save this as OAUTH_APP_ID in your Laravel .env file.
    • Generate and save a client secret (OAUTH_APP_PASSWORD) as well.
  4. Configure Endpoints and Scopes:
    • Set the endpoints and scopes in your .env file. For example:
OAUTH_APP_ID="your-app-id"
OAUTH_APP_PASSWORD="your-client-secret"
OAUTH_REDIRECT_URI="http://localhost:8000/auth/microsoft/callback"
OAUTH_SCOPES='openid profile offline_access User.Read Mail.Read'
OAUTH_AUTHORITY="https://login.microsoftonline.com/common"
OAUTH_AUTHORIZE_ENDPOINT="/oauth2/v2.0/authorize"
OAUTH_TOKEN_ENDPOINT="/oauth2/v2.0/token"

Knowledge of OAuth and API Authentication

Understanding OAuth and API authentication is crucial for working with the Microsoft Graph API. In your Laravel project, you’ll typically need a package like thephpleague/oauth2-client to manage the OAuth flow. You can install it using Composer:

composer require league/oauth2-client

Now that you’ve set up your Laravel project, configured the Azure AD App, and have knowledge of OAuth and API authentication, you’re ready to implement the methods and code we discussed earlier to read, filter, and save Outlook emails. These prerequisites lay the foundation for the successful implementation of your project.

Now we’re going to work on the logic part i.e. redirecting the user to ask for the email read permission, filtering email, making PDF, and saving it into the folder. Let’s get our hands dirty with the code.


Also Read: Typescript Technical Interview Questions 2023 – Part 1


Redirect to Microsoft/Outlook Login

In the first method, we initialize the OAuth client and redirect the user to the Microsoft login page to grant permission to read their Outlook emails. We’ll guide you through the steps:

  • Initialize the OAuth client.
  • Save the user ID in the Laravel session.
  • Generate the authorization URL.
  • Save the client state.
  • Redirect the user to the Microsoft login page.
public function redirectToMicrosoftLogin($userID)
{

    // Initialize the OAuth client
    $oauthClient = new GenericProvider([
      'clientId' => env('OAUTH_APP_ID'),
      'clientSecret' => env('OAUTH_APP_PASSWORD'),
      'redirectUri' => env('OAUTH_REDIRECT_URI'),
      'urlAuthorize' => env('OAUTH_AUTHORITY') . env('OAUTH_AUTHORIZE_ENDPOINT'),
      'urlAccessToken' => env('OAUTH_AUTHORITY') . env('OAUTH_TOKEN_ENDPOINT'),
      'urlResourceOwnerDetails' => '',
      'scopes' => env('OAUTH_SCOPES')
    ]);
    Session::put('o365user_id',$userID);
    $authUrl = $oauthClient->getAuthorizationUrl();

    // Save client state so we can validate in callback
    session(['oauthState' => $oauthClient->getState()]);

    // Redirect to the signin page
    return redirect()->away($authUrl);
}

Handle Microsoft Login Callback

In the second method, we handle the callback after the user accepts or declines access consent. We’ll explain:

  • Validating the state for security.
  • Obtaining the authorization code.
  • Initializing the OAuth client.
  • Making the token request.
  • Saving the access token and redirecting with success or error messages.
public function handleMicrosoftLoginCallback(Request $request)
{
    // Validate state
    $expectedState = session('oauthState');
    $request->session()->forget('oauthState');
    $providedState = $request->query('state');
    if (!isset($expectedState)) {
      // If there is no expected state in the session,
      // do nothing and redirect to the home page.
      return redirect('/admin')->with('error', 'SMTP not configured, Try again');
    }

    // Authorization code should be in the "code" query param
    $authCode = $request->query('code');
    if (isset($authCode)) {
      // Initialize the OAuth client
      $oauthClient = new GenericProvider([
        'clientId' => env('OAUTH_APP_ID'),
        'clientSecret' => env('OAUTH_APP_PASSWORD'),
        'redirectUri' => env('OAUTH_REDIRECT_URI'),
        'urlAuthorize' => env('OAUTH_AUTHORITY') . env('OAUTH_AUTHORIZE_ENDPOINT'),
        'urlAccessToken' => env('OAUTH_AUTHORITY') . env('OAUTH_TOKEN_ENDPOINT'),
        'urlResourceOwnerDetails' => '',
        'scopes' => env('OAUTH_SCOPES')
      ]);

      // StoreTokensSnippet
      try {
        // Make the token request
        $accessToken = $oauthClient->getAccessToken('authorization_code', [
          'code' => $authCode
        ]);
        $user = User::find(Session::get('o365user_id'));
        $user->secret_key = encrypt($accessToken);
        $user->save();
        return redirect('/admin')->with('success', 'Microsoft Office 365 connected successfully');
        
      } catch (IdentityProviderException $e) {
        return redirect('/admin')
          ->with('error', 'Something goes wrong. Please try again.')
          ->with('errorDetail', $e->getMessage());
      } catch (\Throwable $e) {
        return redirect()->back()->with('error', "Unexcepted Error Occured. Contact Support - " . $e->getMessage());
      }
    }
    return redirect('/smtp')
    ->with('error', $request->query('error'))
    ->with('errorDetail', $request->query('error_description'));
} 

Also Read: Laravel Image Compression Project in 10 Minutes


Fetch Emails and Filter for Receipts

This is where the magic happens. In this method, we read Outlook emails, filter for ‘receipt’ keywords in the subject line, and convert matching emails into PDFs. Here’s what we cover:

  • Checking for a fresh access token.
  • Initializing Microsoft Graph.
  • Fetching the user profile.
  • Creating user-specific folders.
  • Fetching emails, iterating through them, and converting them to PDFs.
  • Saving PDFs and email data.
  • Redirecting with success messages.
public function fetchReceipts()
  {

    try {
      $redirectToProfile = false;
      $newReceiptCount = 0;
      $ExistingToken = decrypt(Auth::user()->secret_key) ;
      $accessToken = $this->refreshToken($ExistingToken, Auth::user()->id);
      $graph = new Graph();
      $graph->setAccessToken($accessToken);

      $user = $graph->createRequest('GET', '/me')
        ->setReturnType(\Microsoft\Graph\Model\User::class)
        ->execute();

      if (!$user) {
        dd('2. SMTP not configured, Try again');
        return redirect('/dashboard')->with('error', 'SMTP not configured, Try again');
      }

      $user = json_decode(json_encode($user));
      $folderpath = public_path('storage') . '/' . Auth::user()->id.'/new';

      if (!file_exists($folderpath) && !is_dir($folderpath)) {
        File::makeDirectory($folderpath, $mode = 0777, true, true);
      }

      $files = array();
      $graph->setAccessToken($accessToken);
      $url = "/me/messages?orderby=InferenceClassification, ReceivedDateTime DESC&filter=InferenceClassification eq 'focused'&top=15";

      $response = $graph->createRequest('GET', $url)
        ->setReturnType(\Microsoft\Graph\Model\Event::class)
        ->execute();

      $response = json_decode(json_encode($response));

      if (!empty($response)) {

        $newCategory = Category::where(['user_id'=> Auth::user()->id, 'name'=> 'New'])->first();
        if(!$newCategory){
            $newCategory = Category::create(['name'=> 'New', 'user_id'=> Auth::user()->id]);
        }

        $newFolder = Folder::where(['user_id'=> Auth::user()->id, 'name'=> 'New'])->first();
        if(!$newFolder){
            $newFolder = Folder::create(['name'=> 'New', 'user_id'=> Auth::user()->id, 'parent_id'=> $newCategory->id]);

        }

        foreach ($response as $key => $email) {
          if (
            !empty($email->bodyPreview) &&
            (Str::contains(Str::lower($email->subject), ['receipt']))
          ) {

            $fetchedEmailData = [];
            $fetchedEmailData = ['user_id'=>Auth::user()->id,'recived_from'=>$email->sender->emailAddress->address,'subject'=>Str::lower($email->subject),'received_date'=>$email->receivedDateTime];

            $checkEmailFetched = FetchedEmailData::where($fetchedEmailData)->first();

            if(!$checkEmailFetched){
              $redirectToProfile = true;
              $now = time();

              try {
                Pdf::loadHTML($email->body->content)->save($folderpath . '/' . Carbon::parse($email->receivedDateTime)->format('d-F-Y-h-i-s').'-'.$now . '.pdf');
              } catch (Exception $e) {
                $doc = new DOMDocument();
                $doc->substituteEntities = false;
                $content = mb_convert_encoding($email->body->content, 'html-entities', 'utf-8');
                $doc->loadHTML($content);
                $sValue = $doc->saveHTML();
                Pdf::loadHTML(strip_tags($sValue))->save($folderpath . '/' . Carbon::parse($email->receivedDateTime)->format('d-F-Y-h-i-s').'-'.$now . '.pdf');
              }
  
              \App\Models\File::Create(
              [
                'id' => 1,
                'original_name' => Carbon::parse($email->receivedDateTime)->format('d-F-Y-h-i-s').'-'.$now . '.pdf',
                'path' => 'New\\'.Carbon::parse($email->receivedDateTime)->format('d-F-Y-h-i-s').'-'.$now . '.pdf',
                'user_id' => Auth::user()->id,
                'folder_id' => $newFolder->id,
                'category' => $newCategory->id,
                'created_at' => Carbon::now()->format('Y-m-d'),
              ]);

              $fetchedEmailData['file_name'] = Carbon::parse($email->receivedDateTime)->format('d-F-Y-h-i-s').'-'.$now . '.pdf';
              FetchedEmailData::create($fetchedEmailData);
              $newReceiptCount++;
              array_push($files, 'storage/' . Auth::user()->id . '/new/' . Carbon::parse($email->receivedDateTime)->format('d-F-Y-h-i-s').'-'.$now . '.pdf');

            }
          }
        }
      }

      if($redirectToProfile){
        return redirect('/dashboard/'.$newFolder->id)->with('success', $newReceiptCount.' new receipts found.');
      }else{
          return redirect('/dashboard');
      }

    } catch (IdentityProviderException $e) {
      return redirect('/dashboard')
        ->with('error', 'Something goes wrong. Please try again.')
        ->with('errorDetail', $e->getMessage());
    } catch (\Throwable $e) {
      return redirect()->back()->with('error', "Unexcepted Error Occured. Contact Support - " . $e->getMessage());
    }
  }

Also Read: Learn How to Use the Slack API to Post Messages in Slack Channel Using Laravel


Refresh Token

The fourth method is all about refreshing the access token to ensure seamless email processing. We detail:

  • Checking if tokens exist.
  • Verifying token expiry.
  • Initializing the OAuth client.
  • Refreshing the token.
  • Updating and saving the new token.
  • Error handling.
  • Returning the token.
public function refreshToken($accessToken, $userID)
  {
    
    // Check if tokens exist
    if (empty($accessToken)) {
      return '';
    }

    // Check if token is expired
    //Get current time + 5 minutes (to allow for time differences)
    $now = time() + 300;
    if ($accessToken->getExpires() <= $now) {
      // Token is expired (or very close to it)
      // so let's refresh

      // Initialize the OAuth client
      $oauthClient = new GenericProvider([
        'clientId' => env('OAUTH_APP_ID'),
        'clientSecret' => env('OAUTH_APP_PASSWORD'),
        'redirectUri' => env('OAUTH_REDIRECT_URI'),
        'urlAuthorize' => env('OAUTH_AUTHORITY') . env('OAUTH_AUTHORIZE_ENDPOINT'),
        'urlAccessToken' => env('OAUTH_AUTHORITY') . env('OAUTH_TOKEN_ENDPOINT'),
        'urlResourceOwnerDetails' => '',
        'scopes' => env('OAUTH_SCOPES')
      ]);

      try {
        $newToken = $oauthClient->getAccessToken('refresh_token', [
          'refresh_token' => $accessToken->getRefreshToken()
        ]);

        $user = User::find($userID);
        $user->secret_key = encrypt($newToken);
        $user->save();

        return $newToken->getToken();
      } catch (IdentityProviderException $e) {
        dd($e->getMessage(),$e->getLine());
        return '';
      }
    }

    // Token is still valid, just return it
    return $accessToken->getToken();
  }

Also Read: Learn MongoDB in 14 Steps: A 10 Minutes Guide


FAQs

I’m new to Laravel. Can I still follow the tutorial?

Absolutely! The tutorial includes a dedicated section guiding beginners through setting up a Laravel project. Follow the provided steps, and you’ll be on your way to automating Outlook email processing. If you need additional assistance in setting up Laravel project from scratch, you can refer to this guide on Setting up and installing essential Laravel packages

What if I don’t have a Laravel project set up?

No worries! The article provides instructions on creating a new Laravel project using Composer. Simply follow the outlined steps to set up your project before diving into the Microsoft Graph API integration.

What prerequisites do I need for Microsoft Graph API access?

Before starting, ensure you have a Laravel project, access to the Microsoft Graph API, and knowledge of OAuth and API authentication. The article breaks down these prerequisites and provides code snippets for clarity.

How do I handle OAuth and API authentication in Laravel?

The tutorial covers OAuth and API authentication using league/oauth2-client package. It includes step-by-step explanations and code snippets, making it accessible even if you’re new to these concepts.

What if I encounter errors during the integration process?

Don’t worry! The tutorial includes error-handling sections, addressing common issues. If you face challenges, you can contact me for personalized help.

Can I customize the email filtering process for specific criteria?

Absolutely! The tutorial provides a foundation for reading, filtering, and saving emails. Feel free to modify the code to tailor the filtering process to your specific criteria or requirements.

What if I want to explore more functionalities of the Microsoft Graph API?

Great! The tutorial focuses on automating email processing, but the Microsoft Graph API offers various functionalities. Check Microsoft’s official documentation for a broader understanding and explore additional features as needed. If you have specific questions, reach out to me.

Final Words

With this step-by-step guide, you’re well on your way to automating Outlook email processing using Laravel. You can now easily filter and save ‘receipt’ emails as PDFs in user-specific folders. This not only saves time but also ensures efficient and accurate email management.

The power of Laravel, combined with the Microsoft Graph API, opens up exciting possibilities for streamlining your email processing tasks. Whether you’re an individual or a business, this project can greatly enhance your email management.

Thank you for reading this and I’m sure this will add something great to your knowledge.