Skip to main content

· 3 min read

bcc-header issues with Horde_Mime_Mail

This issue caused some uncertainty since I was not sure if the headers were broken due to missing quotes. See

Turns out that the way I assembled an email from a full text and converting it back to an instance of Horde_Mime_Mail does not consider the type of the internal representation of the bcc-field properly. It's related to Horde_Mail_Rfc822_List::_normalize and how values passed from Horde_Mime_Mail::send() are processed by it.

Here's a code snippet that shows how I use a full text message as input, then converting it back to an instance of Horde_Mime_Mail with headers processed by Horde_Mime_Headers::parseHeaders(). The original message has a bcc header-field:

(Original code can be found here).

         $target = $item->getFullMsg(false);
// ...
$headers = Horde_Mime_Headers::parseHeaders($target);

$mail = new Horde_Mime_Mail($headers);
$part = Horde_Mime_Part::parseMessage($target);

$mailer = $this->getMailer($account);

Horde_Mime_Mail temporarily removes the bcc header and stores it in a property named _bcc, then uses this value to add it to the recipients' addresses later on in send(). This is so the bcc-header is not appearing in the source of the message the recipients receive (see, Section 3.6.3 and 5):

"The "Bcc:" field (where the "Bcc" means "Blind Carbon Copy") contains addresses of recipients of the message whose addresses are not to be revealed to other recipients of the message.", Section 3.6.3

This is a part of Horde_mime_Mail::send():


/* Build recipients. */
$recipients = clone $this->_recipients;
foreach (array('to', 'cc') as $header) {
if ($h = $this->_headers[$header]) {
if ($this->_bcc) {

The source above shows that for to / cc the method getAddressList() is being called, while the value of _bcc gets passed to the add() method. However, _bcc holds in this case an instance of Horde_Mime_Headers_Addresses, which Horde_Mail_Rfc822_List::_normalize() does not consider. The value is ultimately ignored, Emails are not being sent to the addresses mentioned in the bcc header.

Here's the implementation of normalize():


protected function _normalize($obs)
$add = array();

if (!($obs instanceof Horde_Mail_Rfc822_List) &&
!is_array($obs)) {
$obs = array($obs);

foreach ($obs as $val) {
if (is_string($val)) {
$rfc822 = new Horde_Mail_Rfc822();
$val = $rfc822->parseAddressList($val);

if ($val instanceof Horde_Mail_Rfc822_List) {
foreach ($val as $val2) {
$add[] = $val2;
} elseif ($val instanceof Horde_Mail_Rfc822_Object) {
$add[] = $val;

return $add;

A possible fix is to call getAddressList() on _bcc in Horde_Mime_Mail::send() or check for this type in the normalize()-method of Horde_Mail_Rfc822_List:


protected function _normalize($obs)
$add = array();

+ if ($obs instanceof Horde_Mime_Headers_Addresses) {
+ $obs = $obs->getAddressList();
+ }

if (!($obs instanceof Horde_Mail_Rfc822_List) &&
!is_array($obs)) {
$obs = array($obs);

Fixing this in Horde/Mime/Mail.php is also possible, although I do not know if that would cause any side effect since I could not find the expected type of _bcc. It gets checked in '_normalize()' (see above) so I guess this would be the better place to apply the fix, instead of doing this:


/* Build recipients. */
$recipients = clone $this->_recipients;
foreach (array('to', 'cc') as $header) {
if ($h = $this->_headers[$header]) {
if ($this->_bcc) {
- $recipients->add($this->_bcc);
+ $recipients->add($this->_bcc->getAddressList());

Update 21.03.2023: PR available here

· 3 min read

Fix: Missing favicon.ico in Sencha ExtJS production builds

Fixing missing favicon.ico in Ext JS applications.

The original issue is tracked here:

The issue

I’m not quite sure when and why it broke, but it looks like production builds of Sencha Ext JS applications do not contain any favicon.ico originally existing in the development build (anymore).

While everything seems to be okay with development builds (that’s easy, they refer to the development’s root folder in most cases and do not copy and move files around like the production build does), deploying to production will show the default icon coming with your vendor’s browser for any Sencha ExtJS application, at least when your environment is using the following package versions:

    webpack v4.39.3n/a
ext-webpack-plugin v7.6.0
Ext JS v7.6.0.41
Sencha Cmd v7.6.0.87

A call to

$ cross-env webpack

will produce the production build, but the production build will not contain the favicon.ico. Here’s proof (… sort of):

The fix

I did not invest too much investigating the reason why this file is omitted. Instead, I added the copy-webpack-plugin to the project and made sure the the favicon is copied over when running npm run build.

If you’re reading this post, you most likely stumbled across the same issue. Here’s how to fix it.

  1. Add copy-webpack-plugin to your project

I’m still sporting webpack v4.39.3 so I had to fall back to an older version of the plugin. I’m using copy-webpack-plugin@6.4.1 in this case:

    $ npm i -D copy-webpack-plugin@6.4.1
  1. Add a few more modules to your project’s webpack.config.js

Add the following two lines to the head of the file:

    // ....
const CopyWebpackPlugin = require("copy-webpack-plugin");
const fs = require("fs");

Why fs? I couldn’t find an easy way to access the application’s name at this point of the build step through the variables available, so I’m using fs to parse the project's app.json. The value of its name-property is then used for computing the destination folder for the copy-operation.

  1. Add the copy-webpack-plugin to the list of plugins in the script

You’ll easily find the location that has to be edited when looking for the @sencha/ext-webpack-plugin-configuration:

    const plugins = [
new ExtWebpackPlugin({
// ...
new CopyWebpackPlugin({
patterns: [{
from: path.resolve(__dirname, './favicon.ico'),
to: path.join(
path.resolve(__dirname, './app.json')

Subsequent builds will now contain the favicon.ico.

· 2 min read

Releasing conjoon V1.0

I’m happy to announce conjoon 1.0, the very first major release of the Open Source JavaScript Email client.

For updating to the latest version, simply use our installer. It will let you select the latest release when opting for the version to install.


v1.0.0 marks the first major release for our JavaScript Email frontend, over 100 tickets related to bugfixes, optimizations and minor features across all projects where closed.

This release focuses on providing a stable frontend in conjunction with lumen-app-email.

Besides the features already introduced with the release candidates, the following features have been added:


Installer and CLI actions for lumen-app-email

The installation for lumen-app-email has been simplified with the help of Artisan and CLI commands. To get an instance of lumen-app-email running, use

$ composer create-project conjoon/lumen-app-email {targetDir} {version}

which will start the installation process. For more information, refer to the official guide.

Docker Container

ddev-ms-email has been updated to utilize the installer of lumen-app-email and additionally provides integration options for conjoon so that the container can be used for serving both the backend and the frontend.

$ ddev create-conjoon

will start the installation of conjoon. For more information, refer to the official guide.

Happy coding! 🎈

· 3 min read

Easily create Siesta tests for your applications

Siesta is a JavaScript unit and UI testing tool originally written by Mats Bryntse which allows for running tests (for Ext JS (view-) components, amongst others) directly in the web browser (or headless in case you want to use it with your ci tools).

It is conjoon’s favorite among the various testing tools out there (we’re also working with Jest🃏 when there’s no Ext JS involved) and without it, it’s clear that End-to-End tests of some of the features and changes that have made it into conjoon would have slowed the project down due to their cyclic complexity — or even worse, make the software stuck in regression.

extjs-app-webmail alone sports more than 5000 unit and ui tests created with Siesta and they all make sure that the frontend behaves as intended and is free from unwelcomed side effects for any edge case that might occur (we know that’s a bold statement).

To ease the process of setting up a functional Siesta environment, we’re introducing the cli tool create-siesta which can be used with any JavaScript framework that requires a functional Siesta application running in a web browser, or at least a fully fledged infrastructure for running Siesta tests. However, by providing integrated build options for the Ext JS SDK, it is best suited for environments where the Sencha framework is already being used or will be used.

For using the tool, all that is required is a working Node.js installation on your machine. The scaffolding process can then be started by typing

$ npx create-siesta@latest

on the console. create-siesta will then guide you through the installation process and also consider the environment (aka current working directory) it was invoked in, by falling back to already existing Ext JS sources for example, or any other package requirements already available.

Once create-siesta is finished, a folder (defaulting to tests) will be available with your project that contains a scaffolded Siesta environment with templates for additional and future tests you and your team can implement. Tests can then be started with

$ npm run siesta:test

create-siesta builds upon an already available helper tool for creating tests with Siesta, namely **@coon-js/siesta-lib-helper which is already widely used throughout the conjoon** project and the packages it depends on. This means that you will have an additional control at hand with the Siesta application that allows for switching between toolkit dependent tests and dynamically changing timeout values used with


in tests.

*The additional control made available by siesta-lib-helper allows for switching between the modern M and classic C toolkit and provides a list of timeout values that can be globally used with t.waitForMs() in tests.*


The documentation for create-siesta can be found here, the sources are available with the coon.js organization at GitHub, which provides a collection of useful tools for rapid Sencha Ext JS application development, and spawned from the conjoon open source project.

· One min read

JavaScript, function-as-object and the internet

The internet ™️ has caught up on an ad by FERCHAU, found — amongst others — somewhere within the depths of the Berlin subway.

Some devs cringe at the code used with the ad — turns out it can easily get de-mystified with JavaScript’s function-as-object style:

    const careerStuck = () => {};
careerStuck.stop = () => {};

Opinions regarding semantics may differ:

    if (careerStuck() === true) {
beSmart(); // 👀

Further reading

Martin Fowler on FunctionAsObject in an article from 2017. The pattern goes back to the last century when Eugene Wallingford coined the name “Function as Object” in his 1999 pattern language “Envoy”.

· 2 min read

conjoon installer updates

Creating a custom labeled version of conjoon has never been easier.

The installer (i.e. initializer) for conjoon received an update to make setting up a local installation of the frontend more convenient and more intuitive.

The installer requires Node.js 16.14 or above and is started by typing

$ npx create-conjoon@latest

on the command line. Additionally, you have the option to initiate the quick install right away by providing the name and the target directory for the installation:

$ npx create-conjoon@latest "my webmail" ~/webmail

The quick install will continue the installation process by picking the latest release from the package registry (including pre-releases) and configure the installation with fake endpoints returning demo data. You can still connect to production systems later on by using appropriate settings.

If you prefer a guided installation for configuring endpoints for existing backends right away, you can omit any of the arguments. The installer will then present various options to make sure your requirements are met!

In both cases, target directory will contain the files required for developing conjoon and a production build available in the build folder, which is served by typing npm run stage in the target directory.

· One min read

Releasing conjoon 1.0.0-beta.0

🎉 5 years after the (almost) first commit to conjoon’s new home, I’m happy to announce the immediate availability of the first public pre-release of **conjoon**, an open source email client built with JavaScript, PHP and great tools from the open source community.

To install the latest release on your local machine, type

$ npx create-conjoon@latest

then follow the instructions on screen.

Don’t miss out on the documentation for more information about available install and build types.

While I’m confident that conjoon can take its first steps in a production environment, expect a stable first release to be available in the coming days. Until then, a few additions to the documentation and the guides will be made.

Happy coding! 🎈

· 3 min read

Fix: Ext JS Simlets and omitting the status code

I recently stumbled upon a mean error that was hard to track down. Luckily, the issue was caused by a simple carelessness regarding fully configured response-objects of Simlets used in the dev-environment of the conjoon-project and did not require any large refactoring.

The original issue is tracked here:

The issue

The problem was related to batch-operations in Ext JS: When any operation of the batch fails, the pauseonexception-setting is considered and further processing of any remaining operation is halted. The user then has the option to retry the last failed operation, continuing with the remaining operations if this was finally successful. However, retrying some of the failed operations gave the following error:

The operation that failed — and which is now about to be retried — was already destroyed by Ext JS internal garbage-collector.

The fix

It took some time to find out that the operation was destroyed and that some of the properties required for re-running it were already de-referenced. This took me to the internals of response-handling of XMLHttpRequests of Ext JS and finally to the code where the HTTP status of a request was inspected. Take note that I’m mainly developing with Simlets to avoid costly network roundtrips to any backend.

It is mandatory for the Simlets to properly treat mocked backend-errors by also providing the appropriate status-code.

It was assumed that simply setting the success-property in any of the delete/put/...-methods of a simlet treating the request would later on be parsed internally; setting the status-code of the response automatically to anything other than 200. That is not the case. It is mandatory for the Simlets to properly treat mocked backend-errors by also providing the appropriate status-code.

The flaw was too often overseen since the status-field was not set, as the following code examples show (examples represent code in any of the delete/post/put/...-methods of a Json-Simlet; see Ext.ux.ajax.Simlet):

The following leaves the ret.status-property undefined:

const ret = {};
ret.responseText = Ext.JSON.encode({
success: false
return ret;

… while the following re-uses the predefined value of 200 for the status, even if success=false should indicate that the request could not be processed as expected:

me = this,
ret = {};

Ext.Array.forEach(me.responseProps, function (prop) {
if (prop in me) {
ret[prop] = me[prop];

ret.responseText = Ext.JSON.encode({
success: false

return ret;

In both cases, would parse the response and its status in the onComplete-method:

    result =, xhr);

An undefined status would cause the response to be treated as not successful (example 1), while anything other than undefined would be parsed and interpreted according to its HTTP-status representative (successful: 200; failure: 404, 500 etc.).

ExtJS would assume that a successful operation is not needed anymore; it is automatically destroyed then. This was unexpected behavior in this case, since the code marked the operation as a failure, but not via the status-code, which was required.