---
title: "Read an Outlook MSG file in C#"
url: https://recursiveai.org/onemail/cookbook/read-msg-file/
description: "Read an Outlook .msg file in C#: subject, sender, recipients, date, text and HTML bodies and attachments, with no Outlook and no COM. Runnable code with OneMail 0.3.0-preview.2."
version: "0.3.0-preview.2"
---

# Read an Outlook MSG file in C#

Open the file with `Mail.Open` and take its one item: you get the subject, sender, recipients, date, bodies and attachments as plain .NET values. No Outlook, COM or native library is involved. The program below reads a public test message from Apache POI.

```shell
dotnet new console -o ReadMsg && cd ReadMsg
dotnet add package RecursiveAI.OneMail --version 0.3.0-preview.2
curl -LO https://raw.githubusercontent.com/apache/poi/REL_5_4_1/test-data/hsmf/attachment_test_msg.msg
```

OneMail is a preview, so `dotnet add package` needs `--version` or `--prerelease`. Put this in `Program.cs` and run `dotnet run`:

```csharp
using RecursiveAI.OneMail;

// Mail.Open reads the format from the bytes, not the extension:
// .msg, .oft, .eml, .pst and mbox files all open the same way
using MailContainer file = Mail.Open("attachment_test_msg.msg");
IMailItem msg = file.Enumerate().First();

Console.WriteLine($"Format:  {file.Format}");
Console.WriteLine($"Subject: {msg.Subject}");
Console.WriteLine($"From:    {msg.From.FirstOrDefault()}");
Console.WriteLine($"To:      {string.Join(", ", msg.ToRecipients)}");
Console.WriteLine($"Sent:    {msg.Sent?.Value:u}");

Console.WriteLine($"Text body: {msg.TextBody?.ReadText().Trim()}");
Console.WriteLine($"HTML body: {msg.HtmlBody?.ReadText().Length} characters");
Console.WriteLine($"RTF body:  {(msg.RtfBody is null ? "no" : "yes")}");

foreach (IMailAttachment a in msg.Attachments)
    Console.WriteLine($"Attachment: {a.FileName} ({a.Size:N0} bytes)");
```

```text
Format:  Msg
Subject: test pièce jointe 1
From:    Nicolas1 23456 <nicolas1.23456@free.fr>
To:      'nicolas1.23456@free.fr' <nicolas1.23456@free.fr>
Sent:    2009-04-22 14:36:33Z
Text body: contenu
HTML body: 1033 characters
RTF body:  yes
Attachment: test-unicode.doc (24,064 bytes)
Attachment: pj1.txt (89 bytes)
```

`Mail.Open` reads the first bytes to decide the format, so the same call opens `.oft` templates, `.eml` files, PST stores and mbox files. The items share one interface, `IMailItem`.

## The bodies

An Outlook message can carry plain text, HTML and RTF. `TextBody`, `HtmlBody` and `RtfBody` give each one that is present. When Outlook stored the HTML wrapped inside the RTF body, as in this file, `HtmlBody` returns that HTML. When the RTF is the only body, `HtmlBody` is HTML converted from it and its `Origin` is `RtfConverted`: see [Convert an RTF email body to HTML](https://recursiveai.org/onemail/cookbook/convert-rtf-to-html/). Call `ReadText()` for a string, or `Open()` for a stream.

## Attachments

`Attachments` lists files and embedded messages in order. `Size` is the decoded size and `Open()` returns the content. An attached message has `Embedded` set to an `IMailItem` you can read the same way. To save the files, see [Extract attachments from email](https://recursiveai.org/onemail/cookbook/extract-attachments/).

## Limits

- **A preview.** 0.3.0-preview.2: the API can change before 1.0.
- **Reading only.** OneMail can’t write MSG files yet. To get a file other programs read, [convert it to EML](https://recursiveai.org/onemail/cookbook/convert-msg-to-eml/).
- **Damaged files.** The project’s own fidelity table lists this gap: “two fuzz-minimised files from the corpus are refused by the compound file reader (typed defect)”.
- **Exchange senders.** A sender with only an Exchange (X.500) address and no SMTP form has an empty `Address`; `Name` and `X500Address` are still set.

Everything checked, and every known gap: [Fidelity and known gaps](https://recursiveai.org/onemail/docs/compatibility/). How the format is read: [MSG and OFT](https://recursiveai.org/onemail/docs/msg/).
