Last active
April 24, 2024 14:30
-
-
Save DevDuck/db4e7e5903d5d3d2d49a238871da4311 to your computer and use it in GitHub Desktop.
Generate EML file format from an Office Outlook email
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}; |
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
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;
}
} 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