I archived emails to a bunch of EML files, but the content isn't readable when such files are opened as plain text files.

I wonder if there is a converter (must be free) that can bulk convert EML files to plain text files containing subjects, content etc. (attachments could be ignored).

  • 1
    What OS? Must it have a GUI, or is command-line preferred?
    – Izzy
    Commented Jan 19, 2016 at 8:24

2 Answers 2


I took a kinda-working script from here and modified it to fit your needs.

You have to install Python 2.7 for the script to run. When installing Python make sure to select Add python.exe to Path.

Anyways you save the script as [whatever name you want].py and put in the same folder as all your .eml files. Now just open the script. That's it.

It will create a folder Email. That folder has subfolders named the same as your .eml files. In THAT folders you can find all attachments and a .txt file with all email info:

  • From
  • To
  • Subject
  • Date
  • Number of attachments
  • Number of parts
  • The message itself (duh.)

Note that some emails may contain HTML. This script does catch HTML but it saves it as plain text so you have to copy-paste the HTML part of the .txt file, save it as .htm and open it with any web browser.


  • Free ✓

The script is below:

from email import message_from_file
import os

def file_exists (f):
    return os.path.exists(os.path.join(path, f).replace("\\","/"))

def save_file (fn, cont):
    file = open(os.path.join(path, fn).replace("\\","/"), "wb")

def construct_name (id, fn):
    id = id.split(".")
    id = id[0]+id[1]
    return id+"."+fn

def disqo (s):
    s = s.strip()
    if s.startswith("'") and s.endswith("'"): return s[1:-1]
    if s.startswith('"') and s.endswith('"'): return s[1:-1]
    return s

def disgra (s):
    s = s.strip()
    if s.startswith("<") and s.endswith(">"): return s[1:-1]
    return s

def pullout (m, key):
    Html = ""
    Text = ""
    Files = {}
    Parts = 0
    if not m.is_multipart():
        if m.get_filename():
            fn = m.get_filename()
            cfn = construct_name(key, fn)
            Files[fn] = (cfn, None)
            if file_exists(cfn): return Text, Html, Files, 1
            save_file(cfn, m.get_payload(decode=True))
            return Text, Html, Files, 1
        cp = m.get_content_type()
        if cp=="text/plain": Text += m.get_payload(decode=True)
        elif cp=="text/html": Html += m.get_payload(decode=True)
            cp = m.get("content-type")
            try: id = disgra(m.get("content-id"))
            except: id = None
            o = cp.find("name=")
            if o==-1: return Text, Html, Files, 1
            ox = cp.find(";", o)
            if ox==-1: ox = None
            o += 5; fn = cp[o:ox]
            fn = disqo(fn)
            cfn = construct_name(key, fn)
            Files[fn] = (cfn, id)
            if file_exists(cfn): return Text, Html, Files, 1
            save_file(cfn, m.get_payload(decode=True))
        return Text, Html, Files, 1
    y = 0
    while 1:
            pl = m.get_payload(y)
        except: break
        t, h, f, p = pullout(pl, key)
        Text += t; Html += h; Files.update(f); Parts += p
        y += 1
    return Text, Html, Files, Parts

def extract (msgfile, key): 
    m = message_from_file(msgfile)
    From, To, Subject, Date = caption(m)
    Text, Html, Files, Parts = pullout(m, key)
    Text = Text.strip(); Html = Html.strip()
    msg = {"subject": Subject, "from": From, "to": To, "date": Date,
        "text": Text, "html": Html, "parts": Parts}
    if Files: msg["files"] = Files
    return msg

def caption (origin):
    Date = ""
    if origin.has_key("date"): Date = origin["date"].strip()
    From = ""
    if origin.has_key("from"): From = origin["from"].strip()
    To = ""
    if origin.has_key("to"): To = origin["to"].strip()
    Subject = ""
    if origin.has_key("subject"): Subject = origin["subject"].strip()
    return From, To, Subject, Date

if __name__ == "__main__":
    global path

    startdirname = "Email"
    num = 1
    for i in range(10000000):
        if os.path.exists(startdirname + str(num)) == False:
            os.makedirs("Email" + str(num))
            num += 1

    for i in os.listdir("."):
        if i.endswith(".eml") == True:
            nam = i[:-4]
            path = "./" + startdirname + str(num) + "/" + nam

            os.makedirs("./" + startdirname + str(num) + "/" + nam)

            f = open(i, "rb")
            emailDict = extract(f, f.name)

            textFile = ""

            froms = emailDict["from"]
            tos = emailDict["to"]
            subject = emailDict["subject"]
            parts = emailDict["parts"]
            date = emailDict["date"]
            txt = emailDict["text"]
            html = emailDict["html"]

            files = []
            for i in emailDict["files"]:

            textFile += "From: " + froms + "\n"
            textFile += "To: " + tos + "\n"
            textFile += "Subject: " + subject + "\n"
            textFile += "Date: " + date + "\n\n"
            textFile += "Files: " + ", ".join(files) + "\n"
            textFile += "Parts: " + str(parts) + "\n\n"
            textFile += "Text:\n\n" + txt + "\n\n" 
            textFile += "HTML:\n\n" + html

            wf = open("./" + startdirname + str(num) + "/" + nam + "/" + "txt_" + nam + ".txt", "w")

Just realised how old this is. Hope you still need it! :P

  • I get: ``` python2.7 eml2txt.py Traceback (most recent call last): File "eml2txt.py", line 125, in <module> for i in emailDict["files"]: KeyError: 'files' ``` I wonder if it should be msg["files"] instead... Commented Jan 26, 2023 at 10:48
  • nope... don't really have the time to learn python now xD Commented Jan 26, 2023 at 10:49

If you don't mind a little coding then you can do this in python as explained, with sample code, in the answer to this stack overflow question.

Python itself is:

  • Free, gratis & open source
  • Available for just about any platform
  • The referenced example includes information about extracting attachments as well as the content.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.