Skip to content

Instantly share code, notes, and snippets.

@DevDuck
Last active April 24, 2024 14:30
Show Gist options
  • Save DevDuck/db4e7e5903d5d3d2d49a238871da4311 to your computer and use it in GitHub Desktop.
Save DevDuck/db4e7e5903d5d3d2d49a238871da4311 to your computer and use it in GitHub Desktop.
Generate EML file format from an Office Outlook email
import { Buffer } from "buffer";
var item: Office.MessageRead;
Office.onReady(() => {
item = Office.context.mailbox.item;
});
/**
* Gets the email in EML format (IMF text file). This function executes the following steps:
* (1) item.getAllInternetHeadersAsync()
* (2) extract MIME boundary from headers to separate messages
* (3) item.body.getAsync(Office.CoercionType.Html) & base64 encode
* (4) item.body.getAsync(Office.CoercionType.Text) & base64 encode
* (5) iterate through item.attachments & item.getAttachmentContentAsync()
* You can do what you want with the file (string) once complete.
* Note that this code does not encode the attachments into the file.
* @param options - An object literal that contains one or more of the following properties:-
* `asyncContext`: Developers can provide any object they wish to access in the callback function.
* @param callback - Optional. When the method completes, the function passed in the `callback` parameter is called with a single parameter,
* `asyncResult`, which is an `Office.AsyncResult` object.
* On success, the internet headers data is provided in the `asyncResult.value` property as a string.
* Refer to {@link https://tools.ietf.org/html/rfc2183 | RFC 2183} for the formatting information of the returned string value.
* If the call fails, the `asyncResult.error` property will contain an error code with the reason for the failure.
*/
export const getEmailContentAsync = async (
options: Office.AsyncContextOptions,
callback?: (asyncResult: Office.AsyncResult<string>) => void
) => {
await item.getAllInternetHeadersAsync(
options,
await callbackWrapper(options, callback, async (headerResult) => {
let header = headerResult.value;
if (!header) {
let headers = [`From: ${item.from.displayName} <${item.from.emailAddress}>`];
if (item.to && item.to.length)
headers.push(`To: ${item.to.map((e) => `${e.displayName} <${e.emailAddress}>`).join(", ")}`);
if (item.cc && item.cc.length)
headers.push(`CC: ${item.cc.map((e) => `${e.displayName} <${e.emailAddress}>`).join(", ")}`);
headers.push(`Subject: ${item.subject}`);
headers.push(`Date: ${item.dateTimeCreated.toUTCString()}`);
headers.push(`Message-ID: ${item.internetMessageId}`);
header = headers.join("\r\n");
}
await item.body.getAsync(
Office.CoercionType.Html,
callbackWrapper(options, callback, async (htmlResult) => {
await item.body.getAsync(
Office.CoercionType.Text,
callbackWrapper(options, callback, async (result) => {
const boundaryMatches = header.match(/boundary="([\w'()+,-./:=?]+)"/);
const alternativeMatches = header.match(/Content-Type: multipart\/alternative;/);
let boundary: string;
if (boundaryMatches) {
boundary = boundaryMatches[1];
} else {
boundary = `_${item.conversationId.substring(0, 68)}_`;
header += `Content-Type: multipart/mixed;\r\n boundary="${boundary}"\r\n`;
}
let messageBoundary = "_body_" + boundary.slice(6);
if (alternativeMatches) {
// Can skip multipart/mixed content-type header
messageBoundary = boundary;
}
const body = result.value;
const bodyHtml = htmlResult.value;
let content = `${header}MIME-Version: 1.0\r\n`;
if (!alternativeMatches) {
content += `\r\n--${boundary}\r\nContent-Type: multipart/alternative;\r\n boundary="${messageBoundary}"\r\n`;
}
content += `\r
--${messageBoundary}\r
Content-Type: text/plain; charset="utf-8"\r
Content-Transfer-Encoding: base64\r
\r
${base64Chunk(body).join("\r\n")}\r
\r
--${messageBoundary}\r
Content-Type: text/html; charset="utf-8"\r
Content-Transfer-Encoding: base64\r
\r
${base64Chunk(bodyHtml).join("\r\n")}\r
\r
--${messageBoundary}--`;
item.attachments.forEach((a) => {
content += `\r\n\r\n--${boundary}\r
Content-Type: ${a.contentType == "message/rfc822" ? "text/plain" : a.contentType}; name="${a.name}"\r
Content-Description: ${a.name}\r
Content-Disposition: ${a.isInline ? "inline" : "attachment"}; filename="${a.name}"; size=${a.size}`;
// NB: This is where you would inline each attachment. I currently don't have need
// to do this since I save attachments separately, but this is where you might do
// item.getAttachmentContentAsync(a.id, options, ...);
});
if (!alternativeMatches) content += `\r\n\r\n--${boundary}--`;
callback({
asyncContext: options?.asyncContext,
diagnostics: undefined,
error: undefined,
status: Office.AsyncResultStatus.Succeeded,
value: content,
});
})
);
})
);
})
);
};
// Base64 encode string and chunk into 76 char lines
const base64Chunk = (str: string): string[] => {
let encoded = Buffer.from(str).toString("base64");
let maxLength = 76;
let result: string[] = [];
while (encoded.length > maxLength) {
result.push(encoded.slice(0, maxLength));
encoded = encoded.substring(maxLength);
}
result.push(encoded);
return result;
};
// Wrap callback in try/catch block
const callbackWrapper = (
options: Office.AsyncContextOptions,
callback?: (asyncResult: Office.AsyncResult<string>) => void,
processSuccess?: (asyncResult: Office.AsyncResult<string>) => Promise<void>
) => {
return async (asyncResult: Office.AsyncResult<string>) => {
try {
if (asyncResult.status != Office.AsyncResultStatus.Succeeded) {
if (callback) callback(asyncResult);
return;
}
if (processSuccess) await processSuccess(asyncResult);
} catch (err) {
if (callback) callback(wrapException(err, options.asyncContext));
}
};
};
// Wrap exception in asyncResult with Office error
const wrapException = (err, asyncContext?): Office.AsyncResult<any> => {
let officeError = {
code: 5001, // Internal error
name: "getEmailContentAsync",
message: undefined,
};
let asyncResult = {
asyncContext: asyncContext,
diagnostics: err,
error: officeError,
status: Office.AsyncResultStatus.Failed,
value: undefined,
};
if (err instanceof Error) {
officeError.name = err.name;
officeError.message = err.message;
}
return asyncResult;
};
@rajeshthakur1976
Copy link

Hi prmoore77
I am new to outlook add in development , i have to save the mail as .email file as you have mentioned here i used the same approach and same code that you have mentioned like this

try {
await getEmailContentAsync(
{},
async (asyncResult: Office.AsyncResult) => {
if (asyncResult.status != Office.AsyncResultStatus.Succeeded) {
// handle error
return;
}

    const content = asyncResult.value;
    const xx = JSON.stringify(content);
    console.log('content is :' + { xx });

    // upload file
    //Buffer.from(content, 'utf8');
  }
);

} catch (err) {
console.log('Error in saving file');
}

What ever content i am geting from this code that i am saving as .eml file but on opening the saved .eml file body part is block can you pls help me on this pls
Regards
Rajesh

@dheeerajham
Copy link

i also want to get the outlook email in .eml file and upload on the server i am using getAsFileAsync

here is my code
Office.context.mailbox.item.getAsFileAsync({
},function(asyncResult) {
if (asyncResult.status === Office.AsyncResultStatus.Failed) {
return;
}
console.log(asyncResult.value);
});
it's not working

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment