Coffey LabsDWG CL-01
Get in touch
Drawing list

Sheet index

No.SheetContents
A · Company
A-00CoverBuilt to be run, not rented.
A-01ProductsOur software and services, and the smaller tools
A-02CompanyWhere we came from, the founder
A-03NewsCompany announcements
A-04PressDescriptions, facts, brand kit
A-05How we workThe four rules, and the runs behind them
A-06LicensingCopyleft by default, every license
A-07SponsorWhat sponsorship pays for
A-08ContactIssues first, then the addresses
P · Products
P-01inbuxaMail server, console and webmail
P-02ihasmailImmutable webmail for Stalwart
P-03ihasvpnWireGuard server with a web console
P-04Cairn OBSKubernetes-native log aggregation
P-05HotDog CMSOpen source website builder
S · Services
S-01LINUXexpertCertification study guides and practice exams
X · Elsewhere
X-01Forum ↗community.coffeylabs.org
X-02Source code ↗git.coffeylabs.org
X-03GitHub mirror ↗github.com/Coffey-Labs
X-04Discord ↗The hangout
X-05Swag ↗Shirts, hats, mugs and stickers
L · Legal
L-01PrivacyWhat we collect, and your rights
L-02Terms of useThe rules for this site
L-03AI disclosureWhere AI is and isn’t used
↑↓ moveEnter openEsc close
Specification

The account export layout

A directory that holds a whole mail account in open formats: mail as Maildir++, contacts as vCard, calendars as iCalendar, filters as Sieve and files as files, with one manifest.json for what those formats don't hold. Any server or app can read it, and it needs no tool to open.

About this specification
Version1
Date2026-10-30
StatusStable. It is a layout of files, not a protocol element, so no registry is involved.
Change controllerCoffey Labs LLC
Contactspecs@coffeylabs.org

1. Why

Every piece of a mail account already has an open format, but an account is more than its pieces. A folder of .vcf and .ics files doesn't say which address book was the default, which Sieve script was running, what a folder was really called before its name was made safe for a disk, or what the sender identities and signatures were. Without that, a move to another server loses the parts people notice.

This specification fixes where each piece goes and adds one small JSON file for the rest. A server or app that can read Maildir, vCard and iCalendar can take most of an account without reading the manifest at all, and one that reads the manifest can take all of it.

The key words “MUST”, “MUST NOT”, “SHOULD”, “SHOULD NOT” and “MAY” are to be read as described in BCP 14 (RFC 2119 and RFC 8174) when, and only when, they appear in all capitals.

2. The tree

export/
  mail/            Maildir++: the inbox here, other folders as .Parent.Child
  contacts/*.vcf   one file per address book
  calendars/*.ics  one file per calendar
  sieve/*.sieve    one file per script
  files/           the file tree
  manifest.json    what the formats above don't hold
  • An export holds one account. A writer MAY leave out any of the five parts; the manifest says which ones it wrote (section 4).
  • manifest.json MUST be written last. A tree without one is unfinished, and a reader MUST NOT treat it as an export.
  • A writer SHOULD start in a new or empty directory, so nothing from an earlier run is mixed in, and SHOULD make the directory readable by its owner only. It holds someone's mail.

3. The parts

3.1 Mail

mail/ is a Maildir++ tree, the layout Dovecot and Courier serve, so either can serve it as it is.

  • mail/ itself is the inbox. Every other folder is a directory beside it named . followed by its path, levels joined with .: .Archive, .Archive.2025. A folder under the inbox has the prefix INBOX., as in .INBOX.Receipts. Every folder directory holds cur, new and tmp, and every one but the inbox holds an empty maildirfolder file.
  • Each level of a folder's name is in modified UTF-7 (RFC 3501, section 5.1.3). A . or / inside a name, and any character a directory name can't hold, becomes _. Names that clash, even only by case, are numbered: Work, work (2). The manifest keeps every real name.
  • Messages are in cur/, one RFC 5322 message per file, exactly as the source held it. A file's modification time is when the message arrived. Readers MUST NOT depend on the rest of a file's name beyond the Maildir info after :2,.
  • The flags are the standard Maildir letters: D draft, F flagged, P forwarded, R answered, S seen and T deleted. Other keywords, such as $junk or a label, are the letters a to z, named in the folder's dovecot-keywords file as lines of an index and a keyword (0 $junk). That allows 26 per folder. The manifest counts any that didn't fit.
  • A message kept in several folders is written to each. Nothing links the copies, so a reader merging folders into one store sees them as separate messages, as it would reading the same account over IMAP.

3.2 Contacts

contacts/ holds one vCard 4.0 file (RFC 6350) per address book, named after it, with every card in that book. A card in two address books is in both files. Photos and other media the card had are inside it as data: URIs (RFC 2397), so each file stands on its own.

3.3 Calendars

calendars/ holds one iCalendar file (RFC 5545) per calendar, named after it, with one VCALENDAR holding every event and each time zone once. The calendar's name, description, time zone and color are the X-WR-CALNAME, X-WR-CALDESC, X-WR-TIMEZONE and X-APPLE-CALENDAR-COLOR properties calendar apps read; the manifest has them too. Attachments are inside as data: URIs. An event in two calendars is in both files.

3.4 Sieve scripts

sieve/ holds each Sieve script (RFC 5228) as it was, one per file, named after the script with .sieve added. The manifest says which one was active.

3.5 Files

files/ is the account's file storage as directories and files, with their names and modification times. Links are listed in the manifest and never made in the tree: the source chose where they point, and a link could point anywhere on the machine that unpacks it.

3.6 Names on disk

Every name comes from the source, so none is used as a path as it stands. Mail folders follow section 3.1. In every other file or directory name the writer turns / \ : * ? " < > | and control characters into _, drops trailing dots and spaces, and puts _ in front of a name that is empty or starts with a dot. Names Windows reserves (CON, NUL, COM1 and the rest) get a trailing _. Names that clash in one directory, compared without case, are numbered, so the tree unpacks the same on any file system.

4. The manifest

manifest.json is a JSON object (RFC 8259), in UTF-8. Paths in it are relative to the export's root and use /.

Members of the manifest
MemberTypeWhat it holds
formatstring, requiredAlways "https://coffeylabs.org/specs/account-export/", the address of this page. inbuxa-migrate 27.1.0-rc.2 wrote "inbuxa-migrate-files" instead, and a reader MAY accept that as the same format.
formatVersionnumber, requiredThe version of this specification the export follows. This page defines 1.
createdBystring, requiredThe program that wrote the export and its version.
createdstring, requiredWhen it was written, as an RFC 3339 date-time.
sourcesarray, requiredWhere the account came from (below).
mailobject, optionalThe mail tree and its folders.
contactsarray, optionalOne entry per address book.
calendarsarray, optionalOne entry per calendar.
sievearray, optionalOne entry per script.
filesobject, optionalMedia types and links for the file tree.
identitiesarray, optionalThe addresses the account sends as, with their signatures.
participantIdentitiesarray, optionalThe addresses the account uses in calendar invitations.

A part the writer didn't export is left out of the manifest. A part it exported and found empty is there, as an empty array or an object with no entries. A reader MUST tell the two apart: the first says nothing about the account, the second says it had none.

Members of each part

  • sources: objects with kind (how the account was read, such as "jmap", "imap" or "maildir"), url, accountId, accountName (or null) and username. Readers MUST accept kinds they don't know.
  • mail: layout ("maildir++"), path ("mail") and folders, one object per folder: name, its real name; path, an array of the real names from the top level down to this folder; dir, its directory; role, a mailbox role as JMAP names them ("inbox", "sent", "trash" and so on) or null; subscribed; messages, the number of files in it; and, only when some didn't fit, keywordsDropped.
  • contacts: name, description (or null), file, default and cards, the number of cards.
  • calendars: name, description, color and timeZone (each or null), file, default and events.
  • sieve: name, file and active. At most one script is active.
  • files: path ("files"), mediaTypes, an object from each file's path to its media type where the source gave one, and links, an array of {path, target} where target is the link's target as the source described it, or null.
  • identities: name, email, replyTo and bcc (arrays of {name, email}, or null), textSignature and htmlSignature, as in a JMAP Identity (RFC 8621, section 6).
  • participantIdentities: name, calendarAddress (a URI, usually mailto:) and default.

A reader MUST ignore members it doesn't recognize, so a later writer can add members without breaking it. A change a reader has to know about, such as a part moving or a member changing meaning, comes with a new formatVersion. A reader SHOULD refuse a formatVersion it doesn't support rather than guess.

Example

{
  "format": "https://coffeylabs.org/specs/account-export/",
  "formatVersion": 1,
  "createdBy": "example-exporter 1.0",
  "created": "2026-10-30T16:04:11Z",
  "sources": [
    {
      "kind": "jmap",
      "url": "https://mail.example.org/.well-known/jmap",
      "accountId": "c3a",
      "accountName": "alice@example.org",
      "username": "alice@example.org"
    }
  ],
  "mail": {
    "layout": "maildir++",
    "path": "mail",
    "folders": [
      { "name": "Inbox", "path": ["Inbox"], "dir": "mail",
        "role": "inbox", "subscribed": true, "messages": 1204 },
      { "name": "v1.0", "path": ["Projects", "v1.0"], "dir": "mail/.Projects.v1_0",
        "role": null, "subscribed": true, "messages": 37 }
    ]
  },
  "contacts": [
    { "name": "Personal", "description": null, "file": "contacts/Personal.vcf",
      "default": true, "cards": 212 }
  ],
  "calendars": [
    { "name": "Work", "description": null, "color": "#3a7bd5",
      "timeZone": "America/Phoenix", "file": "calendars/Work.ics",
      "default": true, "events": 418 }
  ],
  "sieve": [
    { "name": "filters", "file": "sieve/filters.sieve", "active": true }
  ],
  "files": {
    "path": "files",
    "mediaTypes": { "files/Taxes/2025.pdf": "application/pdf" },
    "links": []
  },
  "identities": [
    { "name": "Alice Smith", "email": "alice@example.org", "replyTo": null,
      "bcc": null, "textSignature": "Alice", "htmlSignature": "" }
  ],
  "participantIdentities": [
    { "name": "Alice Smith", "calendarAddress": "mailto:alice@example.org",
      "default": true }
  ]
}

5. Security considerations

  • Paths in the manifest are data. A writer only names paths inside the tree, but a reader can't know who wrote the export. A reader MUST check that every dir and file resolves inside the export's root before opening it, and MUST NOT follow a symbolic link it finds in the tree.
  • Links are never made. Their targets came from the source and could point anywhere. A reader that recreates them SHOULD do so only inside its own store.
  • The content is untrusted. Messages, cards, events, signatures and files are what the source held, including whatever a sender put in them. HTML signatures in particular SHOULD be sanitized like any other HTML before they are shown.
  • No secrets go in. The format has no place for passwords, tokens, app passwords or keys, and a writer MUST NOT add them. The new server sets its own.

6. Privacy considerations

An export is a complete copy of one person's mail, contacts, calendars and files, and the manifest names the server and the username they came from. It deserves the care the account did: readable by its owner only, and encrypted wherever it is stored or sent.

7. Relation to other formats

  • Maildir and Maildir++ have no RFC. This follows the conventions Courier and Dovecot share, including Dovecot's dovecot-keywords file.
  • mbox (RFC 4155) puts a folder in one file and has no standard place for flags or keywords, so it is not used here.
  • JMAP (RFC 8620, RFC 8621) moves the same data between live servers. This format is for when there is no live server on one side, or for a copy that has to outlast both.

8. Implementations

inbuxa-migrate (opens in a new tab) writes it with export-files, from 27.1.0-rc.2 (which named the format inbuxa-migrate-files), and its import maildir and import takeout commands read the mail, contacts and calendars back. It is open source under Apache-2.0 or MIT.

Changes

  • Version 1, 2026-10-30: the format is named by this page's address instead of inbuxa-migrate-files, so the name belongs to no one tool. Readers may accept the old name.
  • Version 1, 2026-10-30: first version, as inbuxa-migrate 27.1.0-rc.2 writes it.