Sending Email from Node using the Microsoft Graph API

Written by James McDonald

January 30, 2022

First you need to create an Azure AD Application that has Application access to the Mail.Send role of the Microsoft Graph API. You can do this manually or programmatically using Powershell as per my previous

The next step is creating a sample node application that has the following dependencies. Contents on package.json:

{
  "name": "mailsend",
  "version": "1.0.0",
  "description": "Test of mail send through Microsoft Graph",
  "main": "mailSend.js",
  "scripts": {
    "start": "node mailSend.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.25.0",
    "dotenv": "^14.3.2",
    "qs": "^6.10.3"
  }
}

Contents of .env (add this to your .gitignore don’t commit this to the public repo)

TENANT_ID=<your azure Tenant ID>
CLIENT_ID=<Application (client) ID>
CLIENT_SECRET=<Client secret>
AAD_ENDPOINT=https://login.microsoftonline.com
GRAPH_ENDPOINT=https://graph.microsoft.com
[email protected] 
# from is an address in the above azure tenant
SUBJECT=From James McDonald Subject

Contents on mailSend.js

// const fetch = require('node-fetch');
// pulls in the .env file settings
require("dotenv").config();

const axios = require("axios");
const qs = require("qs");

const TENANT_ID = process.env.TENANT_ID || "";
const CLIENT_ID = process.env.CLIENT_ID || "";
const CLIENT_SECRET = process.env.CLIENT_SECRET || "";
const AAD_ENDPOINT = process.env.AAD_ENDPOINT || "";
const GRAPH_ENDPOINT = process.env.GRAPH_ENDPOINT || "";
const from = process.env.FROM || "";
const subject = process.env.SUBJECT || "";

const recipientAddresses = {
  to: [{ address: "[email protected]", name: "A Test DisplayName" }],
  cc: [{ address: "[email protected]", name: "Another Test Display Name" }],
  bcc: [{ address: "[email protected]", name: "A Third Test Display Name" }],
};

function addRecipients(messageBody, rcpts = {}) {
  cloned = Object.assign({}, messageBody);

  Object.keys(rcpts).forEach((element) => {
    if (rcpts[element].length > 0) {
      cloned.message[element + "Recipients"] = createRecipients(rcpts[element]);
    }
  });

  return cloned;
}

function createRecipients(rcpts) {
  return rcpts.map((rcpt) => {
    return {
      emailAddress: {
        address: rcpt.address,
        name: rcpt.name || "",
      },
    };
  });
}

const createEmailAsJson = (rcpts, subject, body) => {
  let messageAsJson = {
    message: {
      subject: subject,
      body: {
        contentType: "HTML",
        content: body,
      },
    },
  };

  messageAsJson = addRecipients(messageAsJson, rcpts);

  return messageAsJson;
};

const getAuthToken = async () => {
  const formData = {
    grant_type: "client_credentials",
    scope: `${GRAPH_ENDPOINT}/.default`,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,
  };

  console.log("url", `${AAD_ENDPOINT}/${TENANT_ID}/oauth2/v2.0/token`);

  const tokenObject = await axios({
    url: `${AAD_ENDPOINT}/${TENANT_ID}/oauth2/v2.0/token`,
    method: "POST",
    data: qs.stringify(formData),
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
  });

  const {
    data: { access_token },
  } = tokenObject;

  return access_token;
};

const sendNotification = async (from, message) => {
  const access_token = await getAuthToken();
  try {
    const response = await axios({
      url: `${GRAPH_ENDPOINT}/v1.0/users/${from}/sendMail`,
      method: "POST",
      headers: {
        Authorization: `Bearer ${access_token}`,
        "Content-Type": "application/json",
      },
      data: JSON.stringify(message),
    });

    console.log("sendNotification status", response.statusText);
  } catch (error) {
    console.log(error);
  }
};

const body = "<h1>Test from James A</h1><p>HTML message sent via MS Graph</p>";

const message = createEmailAsJson(recipientAddresses, subject, body);
console.log(JSON.stringify(message, null, "  "));
sendNotification(from, message);

0 Comments

Submit a Comment

Your email address will not be published.

You May Also Like…

List your VSCode Extensions

Ever wondered what extensions you have installed and want to keep a list? This actually is a good way to audit your...

array_merge vs the + operator

<?php $options = [ 'rootNode' => 'response' ]; // array_merge // the same key appearing later will overwrite echo...