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. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

You May Also Like…

Squarespace Image Export

To gain continued access to your Squarespace website images after cancelling your subscription you have several...

MySQL 8.x GRANT ALL STATEMENT

-- CREATE CREATE USER 'tgnrestoreuser'@'localhost' IDENTIFIED BY 'AppleSauceLoveBird2024'; GRANT ALL PRIVILEGES ON...

Exetel Opt-Out of CGNAT

If your port forwards and inbound and/or outbound site-to-site VPN's have failed when switching to Exetel due to their...