feature request: import address book in LDIF format #174

Closed
opened 2026-09-01 16:27:07 +00:00 by mbunkus · 10 comments

Some groupware systems such as SOGo have address book export in LDIF format (LDIF is the textual representation of contents of an LDAP server such as OpenLDAP or Microsoft's Active Directory). It'd be nice for ihasmail to be able to import that along with the already-existing import of contacts from vCards.

As LDIF only describes the syntax, not the semantics, there are tons of possible LDAP schemes out there. In order not to make this too complex, having support for one specific schema in particular would be nice as it's become a somewhat commonly supported schema, if not a de-facto standard: Mozilla's LDAP address book schema.

Here's an example entry from an export of SOGo:

dn: cn=Jane Doe
objectClass: top
objectClass: inetOrgPerson
objectClass: mozillaAbPersonAlpha
givenName: Jane
description: Description
sn: Doe
cn: Jane Doe
mail: [email protected]
telephoneNumber: +1-555-0199
mobile: +1-555-0188
mozillahomepostalcode: 10000
c: ExampleCountry
postalcode: 10000
l: Examplecity
mozillahomecountryname: ExampleCountry
mozillahomelocalityname: Examplecity
mozillahomestreet: Street Number
street: Street Number

Rebuilt from: notification email, session transcript. Description text is incomplete. Opening date is approximate.

Some groupware systems such as SOGo have address book export in LDIF format (LDIF is the textual representation of contents of an LDAP server such as OpenLDAP or Microsoft's Active Directory). It'd be nice for ihasmail to be able to import that along with the already-existing import of contacts from vCards. As LDIF only describes the syntax, not the semantics, there are tons of possible LDAP schemes out there. In order not to make this too complex, having support for one specific schema in particular would be nice as it's become a somewhat commonly supported schema, if not a de-facto standard: [Mozilla's LDAP address book schema](https://wiki.mozilla.org/MailNews:Mozilla_LDAP_Address_Book_Schema). Here's an example entry from an export of SOGo: ```ldif dn: cn=Jane Doe objectClass: top objectClass: inetOrgPerson objectClass: mozillaAbPersonAlpha givenName: Jane description: Description sn: Doe cn: Jane Doe mail: [email protected] telephoneNumber: +1-555-0199 mobile: +1-555-0188 mozillahomepostalcode: 10000 c: ExampleCountry postalcode: 10000 l: Examplecity mozillahomecountryname: ExampleCountry mozillahomelocalityname: Examplecity mozillahomestreet: Street Number street: Street Number ``` <sub>Rebuilt from: notification email, session transcript. Description text is incomplete. Opening date is approximate.</sub>
Owner

As for the rest: I can only say thanks so many times, soooooo… really appreciate it!

More than happy to accommodate, this has been very useful, I was writing for my own limited use case, your teams insights and reports have really helped (and continue to help) me flesh this out into a pretty awesome application and squash many bugs I would not likely have found on my own.

Date not recovered.

> As for the rest: I can only say thanks so many times, soooooo… really appreciate it! More than happy to accommodate, this has been very useful, I was writing for my own limited use case, your teams insights and reports have really helped (and continue to help) me flesh this out into a pretty awesome application and squash many bugs I would not likely have found on my own. <sub>Date not recovered.</sub>
Owner

Done in #180 — merged, and it'll go out with the next deploy.

Mozilla's schema was the right call to make, and thank you for making it: LDIF is only a syntax, so a file is only readable against a schema, and there was no way to do this generically without either guessing or asking everyone to map their own attributes. Yours is the one Thunderbird and SOGo actually write.

It's read in the browser, since nothing on the server parses LDIF the way it parses vCard. That's split in two on purpose — one part knows RFC 2849 and no attribute names, the other knows your schema and no syntax — so a second schema could be added later without touching the reader.

What comes across: both addresses (the work and home attribute sets become two addresses), every phone kind with work/home/mobile/fax/pager told apart, the second email, organisation with its ou units, job title, nickname, both web pages, and the AIM handle. Your sample entry imports with the work phone and the mobile distinguished, both addresses, and the description as the note.

Three decisions I'd rather state than have you discover:

  1. The four mozillaCustom fields are appended to the note, labelled the way Thunderbird labels them. JSContact has no equivalent and the schema doesn't say what they hold, so the choice was between keeping them somewhere visible and dropping them. If your colleagues' exports have those fields full of noise rather than notes, say so and I'll drop them instead.
  2. An entry with neither a name nor an email address is skipped. It would otherwise arrive as a blank row — impossible to identify, and tedious to hunt down and delete.
  3. The dn is not reused as the contact's identity. It says where the entry sat in whoever's directory it came from, which is not an identity anywhere else, so each imported card gets its own.

The import control now takes either format and decides by what's in the file rather than by its name, since an address book exported as LDIF turns up as .ldif, .ldi, .txt or with no extension at all. One consequence worth knowing: the schema has no birthday attribute, so birthdays aren't in an LDIF export to begin with — nothing is being dropped there.

Done in #180 — merged, and it'll go out with the next deploy. Mozilla's schema was the right call to make, and thank you for making it: LDIF is only a syntax, so a file is only readable against a schema, and there was no way to do this generically without either guessing or asking everyone to map their own attributes. Yours is the one Thunderbird and SOGo actually write. It's read in the browser, since nothing on the server parses LDIF the way it parses vCard. That's split in two on purpose — one part knows RFC 2849 and no attribute names, the other knows your schema and no syntax — so a second schema could be added later without touching the reader. What comes across: both addresses (the work and home attribute sets become two addresses), every phone kind with work/home/mobile/fax/pager told apart, the second email, organisation with its `ou` units, job title, nickname, both web pages, and the AIM handle. Your sample entry imports with the work phone and the mobile distinguished, both addresses, and the description as the note. Three decisions I'd rather state than have you discover: 1. **The four `mozillaCustom` fields are appended to the note**, labelled the way Thunderbird labels them. JSContact has no equivalent and the schema doesn't say what they hold, so the choice was between keeping them somewhere visible and dropping them. If your colleagues' exports have those fields full of noise rather than notes, say so and I'll drop them instead. 2. **An entry with neither a name nor an email address is skipped.** It would otherwise arrive as a blank row — impossible to identify, and tedious to hunt down and delete. 3. **The `dn` is not reused as the contact's identity.** It says where the entry sat in whoever's directory it came from, which is not an identity anywhere else, so each imported card gets its own. The import control now takes either format and decides by what's *in* the file rather than by its name, since an address book exported as LDIF turns up as `.ldif`, `.ldi`, `.txt` or with no extension at all. One consequence worth knowing: the schema has no birthday attribute, so birthdays aren't in an LDIF export to begin with — nothing is being dropped there.
Author

Thanks again!

Skipping entries without name/email address is perfectly fine.

The custom fields are… well… yeah. Not sure if a great solution is even possible here. Personally I'd rather err on the side of caution, meaning keeping them instead of silently discarding them. So I'm fine with that, too.

An object's distinguished name is indeed not an identifier, for various reasons (position may change, MUST attributes of the structural class may be changed which changes the DN etc.). LDAP directories usually carry some kind of unique object ID element for that very reason, and that one is usually managed by the server itself (in Active Directory it's objectGUID, in OpenLDAP it's entryUUID which is usually not exposed as an attribute unless the LDAP search query explicitly requests internal attributes). Furthermore importing such LDIFs is likely most often done during one-way migrations from other systems — and the DN often only made sense in the old system, not necessarily in the new. Customers with special needs will simply have their LDIFs pre-processed with custom scripts in our case.

Thanks again! Skipping entries without name/email address is perfectly fine. The custom fields are… well… yeah. Not sure if a _great_ solution is even possible here. Personally I'd rather err on the side of caution, meaning keeping them instead of silently discarding them. So I'm fine with that, too. An object's distinguished name is indeed not an identifier, for various reasons (position may change, `MUST` attributes of the structural class may be changed which changes the DN etc.). LDAP directories usually carry some kind of unique object ID element for that very reason, and that one is usually managed by the server itself (in Active Directory it's `objectGUID`, in OpenLDAP it's `entryUUID` which is usually not exposed as an attribute unless the LDAP search query explicitly requests internal attributes). Furthermore importing such LDIFs is likely most often done during one-way migrations from other systems — and the DN often only made sense in the old system, not necessarily in the new. Customers with special needs will simply have their LDIFs pre-processed with custom scripts in our case.
Author

Some comments from my colleague after testing:

Fields I'd expect were propagated.

Some nitpicks:

  • No duplicate checking
    • Description: re-importing causes duplicate contacts
    • Suggested change: implement duplicate checking
  • Inconsistent ui - nav-more btn:
    • Description: Calendar page has the nav-more btn on hover. The contact page however does not have this nav-more btn on hover, but has a right-click menu.
    • Suggested change: Implement the nav-more btn on the contact page
  • Inconsistent ui - flex containers import/export contacts
    • Description: There are two main buttons for import/exporting contacts, while each address book has a submenu.
    • Suggested change: Move those buttons to the addresses book submenu
Some comments from my colleague after testing: Fields I'd expect were propagated. Some nitpicks: - No duplicate checking - Description: re-importing causes duplicate contacts - Suggested change: implement duplicate checking - Inconsistent ui - nav-more btn: - Description: Calendar page has the nav-more btn on hover. The contact page however does not have this nav-more btn on hover, but has a right-click menu. - Suggested change: Implement the nav-more btn on the contact page - Inconsistent ui - flex containers import/export contacts - Description: There are two main buttons for import/exporting contacts, while each address book has a submenu. - Suggested change: Move those buttons to the addresses book submenu
Owner

Taking your colleague's three points in reverse order of how much I have to show for them.

None of the three are done yet. What follows is where each stands, plus a bug she didn't report that I fixed while I was in there.

Duplicate checking on re-import. This is now the open question in two places — here for contacts, and on #173 where you were still thinking about UID matching for calendars. Both come down to the same decision, so I'd rather make it once. For contacts the wrinkle is that a vCard UID is reliable, an LDIF entry's identity is not: the schema has no UID, and the dn is the thing you and I already agreed isn't an identity. So matching on re-import would work for vCard, and for LDIF would have to fall back to something softer — name plus email, probably — which is a guess and can be wrong in both directions. My inclination is to skip on UID where there is one and import where there isn't, and to not invent a match for LDIF. If you'd rather have the softer match, that's a reasonable different answer and I'd want to hear it before building it.

The two UI inconsistencies. Both are fair and both are real. Contacts and calendar grew their menus at different times and it shows. Wanting the hover nav-more button on contacts, and the global import/export buttons folded into the address book's own submenu, would make contacts follow the calendar — which is also where the iCAL import ended up, so there'd be one pattern rather than two. I haven't done it yet.

And a bug she didn't report. After the 800 KB iCAL failure on #173 I went looking for the same mistake elsewhere and found it here: ContactCard/set was sending everything in one call in three places — the vCard import, the LDIF import, and bulk delete. A large enough address book would have imported nothing at all, and select-all-then-delete would have deleted nothing, both with the same requestTooLarge your colleague pasted. Fixed in #218, live now as 2026.9.2+pr219, and it splits by the ceiling your server advertises rather than a hardcoded 500.

Two smaller things that came with it: deleting now takes off the list what the server actually confirmed it destroyed, not everything that was asked for. And a vCard import the server refused every card of used to report "imported 0 contacts", which reads as an empty file rather than a refusal — it now says why, which the LDIF import has done since you filed this.

Please pass on thanks to your colleague. The nitpicks are the useful kind.

Taking your colleague's three points in reverse order of how much I have to show for them. **None of the three are done yet.** What follows is where each stands, plus a bug she didn't report that I fixed while I was in there. **Duplicate checking on re-import.** This is now the open question in two places — here for contacts, and on #173 where you were still thinking about UID matching for calendars. Both come down to the same decision, so I'd rather make it once. For contacts the wrinkle is that a vCard UID is reliable, an LDIF entry's identity is not: the schema has no UID, and the `dn` is the thing you and I already agreed isn't an identity. So matching on re-import would work for vCard, and for LDIF would have to fall back to something softer — name plus email, probably — which is a guess and can be wrong in both directions. My inclination is to skip on UID where there is one and import where there isn't, and to *not* invent a match for LDIF. If you'd rather have the softer match, that's a reasonable different answer and I'd want to hear it before building it. **The two UI inconsistencies.** Both are fair and both are real. Contacts and calendar grew their menus at different times and it shows. Wanting the hover nav-more button on contacts, and the global import/export buttons folded into the address book's own submenu, would make contacts follow the calendar — which is also where the iCAL import ended up, so there'd be one pattern rather than two. I haven't done it yet. **And a bug she didn't report.** After the 800 KB iCAL failure on #173 I went looking for the same mistake elsewhere and found it here: `ContactCard/set` was sending everything in one call in three places — the vCard import, the LDIF import, and bulk delete. A large enough address book would have imported nothing at all, and select-all-then-delete would have deleted nothing, both with the same `requestTooLarge` your colleague pasted. Fixed in #218, live now as `2026.9.2+pr219`, and it splits by the ceiling your server advertises rather than a hardcoded 500. Two smaller things that came with it: deleting now takes off the list what the server actually confirmed it destroyed, not everything that was asked for. And a vCard import the server refused every card of used to report "imported 0 contacts", which reads as an empty file rather than a refusal — it now says why, which the LDIF import has done since you filed this. Please pass on thanks to your colleague. The nitpicks are the useful kind.
Author

None of the three are done yet.

Understood.

Duplicate checking on re-import.

LDIF …

I think the most common case of duplicates-on-import is when you do an actual migration. At that point you import, see something didn't work quite right, and want to import again. This most often happens when you have to pre-process the content to import somehow, converting from the source's idiosyncrasies to the format expected by the destination.

This workflow requires you to be able to quickly delete all imported entries & start over.

In other instances users who aren't that technically deep want to import, the browser hickups for whatever reason, or they're distracted for a quick moment & forget whether they've already imported, and import again.

In either case the time between consecutive imports isn't that large.

What I'm trying to argue here is that using one of the attributes that can change (e.g. due to legal name changes, or moves to different LDAP tree locations) should be fine as they won't actually change between imports.

I agree there is no way to bullet-proof LDIF import wrt. much longer time periods between imports.

Personally I like good default choices a lot, with "good" being vague but usually meaning that most people using a feature would not need to configure it, and it would work as most people expect it out of the box. Therefore a) implementing a good attempt at duplicate detection should be done & b) using one of the potentially changing attributes will suffice.

When in doubt present a dialog after import stating how many were detected as duplicates & how many were newly created (if that isn't happening already; personally I haven't actually given the import a try yet).

My inclination is to skip on UID…

That contains an aspect we haven't discussed before: whether to skip if an existing entry is found or to update the existing entry. Going with the "do what most people expect" I consider "update existing entries if existing entry via $criterium is found" to be the right thing here, not skipping importing the entry.

At least for the contact import — and no matter the source format (LDIF, vCard).

And a bug she didn't report.

Thanks for extending the scope. It's always good to have all instances of a problem eliminated.

As for the rest: I can only say thanks so many times, soooooo… really appreciate it!

> None of the three are done yet. Understood. > Duplicate checking on re-import. > … > LDIF … I think the most common case of duplicates-on-import is when you do an actual migration. At that point you import, see something didn't work quite right, and want to import again. This most often happens when you have to pre-process the content to import somehow, converting from the source's idiosyncrasies to the format expected by the destination. This workflow requires you to be able to quickly delete all imported entries & start over. In other instances users who aren't that technically deep want to import, the browser hickups for whatever reason, or they're distracted for a quick moment & forget whether they've already imported, and import again. In either case the time between consecutive imports isn't that large. What I'm trying to argue here is that using one of the attributes that can change (e.g. due to legal name changes, or moves to different LDAP tree locations) should be fine as they _won't_ actually change between imports. I agree there is no way to bullet-proof LDIF import wrt. much longer time periods between imports. Personally I like good default choices a lot, with "good" being vague but usually meaning that most people using a feature would not need to configure it, and it would work as most people expect it out of the box. Therefore a) implementing a good attempt at duplicate detection should be done & b) using one of the potentially changing attributes will suffice. When in doubt present a dialog after import stating how many were detected as duplicates & how many were newly created (if that isn't happening already; personally I haven't actually given the import a try yet). > My inclination is to skip on UID… That contains an aspect we haven't discussed before: whether to _skip_ if an existing entry is found or to _update_ the existing entry. Going with the "do what most people expect" I consider "update existing entries if existing entry via `$criterium` is found" to be the right thing here, _not_ skipping importing the entry. At least for the _contact_ import — and no matter the source format (LDIF, vCard). > And a bug she didn't report. Thanks for extending the scope. It's always good to have all instances of a problem eliminated. As for the rest: I can only say thanks so many times, soooooo… really appreciate it!
Owner

Both of your colleague's remaining points are now issues of their own, so they don't sit in a closed thread: #223 for duplicate checking on re-import, and #224 for the two menu inconsistencies (the missing hover button, and the global import/export buttons that belong in the address book's own menu).

#223 still carries the open question from my last comment — whether LDIF should fall back to a name+email match given it has no UID. No rush; the vCard half can move without it.

Both of your colleague's remaining points are now issues of their own, so they don't sit in a closed thread: #223 for duplicate checking on re-import, and #224 for the two menu inconsistencies (the missing hover button, and the global import/export buttons that belong in the address book's own menu). #223 still carries the open question from my last comment — whether LDIF should fall back to a name+email match given it has no UID. No rush; the vCard half can move without it.
Owner

Both of your colleague's menu points are done, in #226 — live now as 2026.9.2+pr226. They were tracked as #224, now closed.

The hover button. Address books have it now, exactly as the calendars do, and right-click still works. It turned out to cost nothing: the rows were already using the same element as the mail folders, which has carried the hover-reveal rule since it was written. The button was simply never added.

Import and export are in the menus. You were right that two buttons which don't say which book they act on is the wrong shape — the calendar had already settled this, which is why its iCAL import lives in the calendar's own menu, and contacts was the module that hadn't caught up.

Your colleague's remark found a bug neither of us was looking for. Export handed you the list on screen, so a search box with anything in it quietly narrowed the exported file — you'd ask for the book and get whatever happened to be showing. Harmless while the button sat directly under that list; wrong the moment it moves to a menu in the sidebar. Exporting a book now exports that book.

Two things I kept deliberately, since removing the buttons would otherwise have taken them:

  • "All contacts" has the same menu, so exporting everything still has a home. Importing there files into your default book, which is what the old button quietly did anyway.
  • A book somebody shared with you now has a menu rather than the bare ✕. Someone else's book can be exported too, and losing that would have been a regression dressed up as a tidy-up. The consequence, and I'd rather say it than have you find it: removing a shared book is two clicks now instead of one. If that annoys anyone in practice, say so and I'll put the ✕ back alongside the menu.

Still open from your colleague's list: duplicate checking, which is #223. The vCard half is straightforward — same rule as the calendar, which shipped this morning. The LDIF half is the question I asked earlier and haven't heard back on: with no UID in the schema and the dn being no use as an identity, matching would have to fall back to name-plus-email, which guesses in both directions. My inclination is not to guess, and to let LDIF re-imports duplicate as they do now. No rush, and the vCard half can move without an answer.

Thanks again to your colleague. Three reports, three real things — one of them a bug that had nothing to do with what she was reporting.

Both of your colleague's menu points are done, in #226 — live now as `2026.9.2+pr226`. They were tracked as #224, now closed. **The hover button.** Address books have it now, exactly as the calendars do, and right-click still works. It turned out to cost nothing: the rows were already using the same element as the mail folders, which has carried the hover-reveal rule since it was written. The button was simply never added. **Import and export are in the menus.** You were right that two buttons which don't say which book they act on is the wrong shape — the calendar had already settled this, which is why its iCAL import lives in the calendar's own menu, and contacts was the module that hadn't caught up. Your colleague's remark found a bug neither of us was looking for. Export handed you *the list on screen*, so a search box with anything in it quietly narrowed the exported file — you'd ask for the book and get whatever happened to be showing. Harmless while the button sat directly under that list; wrong the moment it moves to a menu in the sidebar. Exporting a book now exports that book. Two things I kept deliberately, since removing the buttons would otherwise have taken them: - **"All contacts" has the same menu**, so exporting everything still has a home. Importing there files into your default book, which is what the old button quietly did anyway. - **A book somebody shared with you now has a menu** rather than the bare ✕. Someone else's book can be exported too, and losing that would have been a regression dressed up as a tidy-up. The consequence, and I'd rather say it than have you find it: removing a shared book is two clicks now instead of one. If that annoys anyone in practice, say so and I'll put the ✕ back alongside the menu. Still open from your colleague's list: duplicate checking, which is #223. The vCard half is straightforward — same rule as the calendar, which shipped this morning. The LDIF half is the question I asked earlier and haven't heard back on: with no UID in the schema and the `dn` being no use as an identity, matching would have to fall back to name-plus-email, which guesses in both directions. My inclination is not to guess, and to let LDIF re-imports duplicate as they do now. No rush, and the vCard half can move without an answer. Thanks again to your colleague. Three reports, three real things — one of them a bug that had nothing to do with what she was reporting.
Owner

The last loose end from this thread is shipped: live now as 2026.9.4+pr278.

You described the migration workflow as import, spot something wrong, delete what came in, correct the export, start over — and noted that it needs a quick way to delete all the imported entries. There was none. Recorded as #277, and built.

Emptying an address book is in the book's own menu, beside the import and export that moved there in #226. It is separate from Delete, which takes the book with it — a default book cannot be deleted and can perfectly well be emptied, which is most of why it earns its own entry.

Selecting contacts came with it, because the gap was wider than what you asked for: contacts had no multi-select at all. The only delete in the module was the cross on a single card's pane, one card and one confirmation at a time. So "delete these fourteen" was as missing as "empty this book". The list has checkboxes now, hover-revealed as the message list's are and always visible on a touchscreen, with shift-click taking the run between two rows.

Three things worth knowing before you rely on it:

A card filed in two address books is removed from the one being emptied and left alone in the other, rather than deleted. ContactCard/set destroy takes a card away from every book at once, so the obvious implementation would have quietly emptied a book nobody asked about. That count is reported separately afterwards, or it looks like contacts that refused to go.

Emptying somebody else's shared book is deliberately not offered. Those cards live in the owner's account and this client has no path to write there, so the option would be one that cannot work.

There is no undo. ContactCard/set destroy has no trash to land in, which is why both the selection delete and the empty say so in the confirmation and name the count.

This was built from your description of the workflow rather than from anything you specified, so if the shape is wrong — if what you actually want is "delete everything this import just added" rather than "empty this book" — say so. That is a different feature and a reasonable thing to want, since the second is only the first when the book was empty to begin with.

Worth pairing with the other half, which shipped earlier today: an LDIF re-import now matches on the entry's dn and updates rather than duplicating (#223). Between the two, a corrected export should no longer need a clean slate to land on — though emptying is still there for when the first import brought in entries that should not exist at all, which correcting the file does not remove.

The last loose end from this thread is shipped: **live now as `2026.9.4+pr278`.** You described the migration workflow as import, spot something wrong, delete what came in, correct the export, start over — and noted that it needs a quick way to delete all the imported entries. There was none. Recorded as #277, and built. **Emptying an address book** is in the book's own menu, beside the import and export that moved there in #226. It is separate from Delete, which takes the book with it — a default book cannot be deleted and can perfectly well be emptied, which is most of why it earns its own entry. **Selecting contacts** came with it, because the gap was wider than what you asked for: contacts had no multi-select at all. The only delete in the module was the cross on a single card's pane, one card and one confirmation at a time. So "delete these fourteen" was as missing as "empty this book". The list has checkboxes now, hover-revealed as the message list's are and always visible on a touchscreen, with shift-click taking the run between two rows. **Three things worth knowing before you rely on it:** A card filed in two address books is removed from the one being emptied and left alone in the other, rather than deleted. `ContactCard/set destroy` takes a card away from every book at once, so the obvious implementation would have quietly emptied a book nobody asked about. That count is reported separately afterwards, or it looks like contacts that refused to go. Emptying somebody else's shared book is deliberately not offered. Those cards live in the owner's account and this client has no path to write there, so the option would be one that cannot work. There is no undo. `ContactCard/set destroy` has no trash to land in, which is why both the selection delete and the empty say so in the confirmation and name the count. **This was built from your description of the workflow rather than from anything you specified**, so if the shape is wrong — if what you actually want is "delete everything this import just added" rather than "empty this book" — say so. That is a different feature and a reasonable thing to want, since the second is only the first when the book was empty to begin with. Worth pairing with the other half, which shipped earlier today: an LDIF re-import now matches on the entry's `dn` and updates rather than duplicating (#223). Between the two, a corrected export should no longer need a clean slate to land on — though emptying is still there for when the first import brought in entries that should not exist at all, which correcting the file does not remove.
Owner

One correction to my last two comments, before you go looking for any of this.

Where I wrote "live now as 2026.9.4+pr276" and "2026.9.4+pr278", I meant the deployed instance at ihasmail.com. Both changes are merged to main, but neither is in a published image yet — the newest release is still v2026.9.3-pr272, which is what you pulled. My wording, and it reads as though there is something to pull today.

latest is built by automation on Mondays where the previous week had changes, so the next one is Monday 2026-09-07 at 09:00 UTC — 11:00 your time, and will carry both: the LDIF dn matching and the address book emptying. Treat the hour as approximate rather than a promise: GitHub runs scheduled workflows on a best-effort basis and delays them when its queue is busy, sometimes by a good while. Allow for the multi-arch build on top of that.

If you would rather not wait, building from main gets you them now — and the offer from earlier stands, if that npm problem on your test machine ever becomes worth ten minutes to either of us. Otherwise Monday, and nothing is expected of you before then.

One correction to my last two comments, before you go looking for any of this. Where I wrote "live now as `2026.9.4+pr276`" and "`2026.9.4+pr278`", I meant the deployed instance at ihasmail.com. Both changes are merged to `main`, but neither is in a published image yet — the newest release is still **`v2026.9.3-pr272`**, which is what you pulled. My wording, and it reads as though there is something to pull today. `latest` is built by automation on Mondays where the previous week had changes, so the next one is **Monday 2026-09-07 at 09:00 UTC — 11:00 your time**, and will carry both: the LDIF `dn` matching and the address book emptying. Treat the hour as approximate rather than a promise: GitHub runs scheduled workflows on a best-effort basis and delays them when its queue is busy, sometimes by a good while. Allow for the multi-arch build on top of that. If you would rather not wait, building from `main` gets you them now — and the offer from earlier stands, if that `npm` problem on your test machine ever becomes worth ten minutes to either of us. Otherwise Monday, and nothing is expected of you before then.
This repo is archived. You cannot comment on issues.