General overview about SnappyMail
⚠ Please note that this documentation is not complete yet - any help is appreciated. ⚠
History of SnappyMail
SnappyMail is a fork of the RainLoop Webmail Project. RainLoop was initially written by Timur Usenko who worked at the company AfterLogic and who wrote the MailSo library. In RainLoop and SnappyMail MailSo is used to handle the main part of the IMAP communication. The SnappyMail fork was initiated by the-djmaze because RainLoop does not seemed to be maintained regularly and had multiple security issues. SnappyMail therefore contains the code of Rainloop, but has been modified a lot to bring it to a next level.
Logging and debugging
Please have a look to https://github.com/the-djmaze/snappymail/wiki/FAQ#how-do-i-enable-logging how you can enable logging inside of SnappyMail.
Developers should additionally know that the logs normally do not contain passwords and other sensitive data because MailSo\Log\Logger::Write
replaces those words by *******
.
If you really have to log passwords you can set the parameter bool $bSearchSecretWords
of the function Logger::Write
to false
or modify the configuration of SnappyMail by changing the switch hide_passwords
inside of application.ini
.
SnappyMail front-end
The current front-end of SnappyMail is written in KnockoutJS and communicates with the back-end by JSON-format data. SnappyMail tries to render the most on the clients side to reduce server load on big installations. More details on why at the moment KnockoutJS is used can be read here.
The SnappyMail Extensions System (Plugins)
General information
Plugins extend the functionality of SnappyMail. Administrators of a SnappyMail installation can activate extensions by entering in the Admin Panel of SnappyMail -> menu "Extensions" and checking the checkbox "Enable extensions".
On the same menu you can find a list of plugins available for installation. This list points to the official plugin repository of SnappyMail. The source code of these plugins can be found here.
The source code of installed extensions is placed on your webserver inside the SnappyMail folder under _data_/_default_/plugins/
. Therefore you can also install "non official" extensions or plugins in development by coping their code into a subfolder of this folder - keep in mind to set the correct access rights on this new files.
How SnappyMail initializes the plugins
The following shall describe how SnappyMail initializes the activated extensions. All files can be found in the SnappyMail repository under snappymail/v/0.0.0/ and the following paths are relative to that folder.
include.php
callsRainloop\Service::Handle()
inside ofapp/libraries/RainLoop/Service.php
Service.php
callsApi::Actions()
inside ofapp/libraries/RainLoop/Api.php
.- Additional info: Service.php afterwards goes ahead initializing for example the caching functions etc. and ends with a launch of the function BootEnd() in Actions.php
Api.php
creates the object$oActions
by the classapp/libraries/RainLoop/Actions.php
.- The constructor of
Actions.php
creates an object$oPlugins
by the classapp/libraries/RainLoop/Plugins/Manager.php
. - The constructor of
Manager.php
searches for active plugins. It then calls theInit()
function of every active plugin.
Because the Init()
function of every plugin is called by Manager.php
inside this function each plugin can register itself to be launched (=callback function) at various points in the code of SnappyMail (=hooks).
Hooks and therefore your registered functions of the plugin are called on multiple points inside the source of SnappyMail by launching the function RunHook
of app/libraries/RainLoop/Plugins/Manager.php
.
Getting started with your plugin
Plugins always extend the AbstractPlugin class.
To get an idea what is possible see the example plugin and the other plugins in the plugin repository.
You plugin class inside the index.php
of your plugin first should declare some information about it. This info will be shown inside the Admin Panel -> Extensions Menu and lets the user know what the plugin is intended for or if a new version of your plugin is available.
const
NAME = 'Avatars',
AUTHOR = 'SnappyMail',
URL = 'https://snappymail.eu/',
VERSION = '1.5',
RELEASE = '2022-12-15',
REQUIRED = '2.23.0',
CATEGORY = 'Contacts',
LICENSE = 'MIT',
DESCRIPTION = 'Show graphic of sender in message and messages list (supports BIMI, Gravatar and identicon, Contacts is still TODO)';
In many cases your plugin will also need some configuration. For example the administrator could insert credentials for a database connection that your plugin needs to work properly. All these text fields, dropdown etc. are defined inside your plugin class inside protected function configMapping(): array
. See for example this plugin to get an idea what you need to define inside configMapping()
. SnappyMail will take this definitions and create a configuration dialog that is reachable inside the Extensions menu of the Admin Panel (little cogwheel beside the name of your plugin).
The list of available PluginPropertyType
(text fields, dropdown boxes...) can be found here.
Hooks
Register your plugin to a specific hook
The Init()
function of a plugin can register one or more functions to be called when SnappyMail hits a specific hook inside the code. This can happen for example when a user inserts his credentials, an IMAP connection was successful or a message is going to be saved.
To register the function myCallbackFunction
inside of your index.php of your extension to the hook login.success
you have to add the following to your Init()
function:
$this->addHook('login.success', 'myCallbackFunction');
If in SnappyMail the hook login.success
is executed, SnappyMail will pass an array with parameters to the function RunHook
defined in snappymail/v/0.0.0/app/libraries/RainLoop/Plugins/Manager.php
. The length and content of the array with parameters depends on the specific hook. The function RunHook
takes the array and passes the content as single parameters to your function myCallbackFunction
- it will not hand over the array.
Therefore the example function myCallbackFunction
should be defined to handle one parameter Account $oAccount
because login.success
passes you over the object of the account that was logged on.
Finding the best hook for your plugin
A possible way to find an ideal hook for your extension is to do a fulltext search for the string RunHook(
over the SnappyMail source code. This returns every active hook and his circumstances like parameters that are passed to your callback function.
Also, this is the only way to make sure that your plugin is called at the moment you need.
Available hooks
For a complete list of available hooks inside of SnappyMail see https://github.com/the-djmaze/snappymail/blob/master/plugins/README.md#hooks. Here we will describe some hooks to let you get an idea what in detail is described inside the README.md.
Example: login.credentials.step-1
Hook will run before the checks if the given username (=mail address) of a user is valid and contains a domain-part. This hook can modify the mail address before other checks are done if this is necessary.
Parameters passed to the plugins:
string &$sEmail
Example: login.credentials.step-2
Hook will run after the checks described in login.credentials.step-1 when also the password of the users who tries to log in is available.
Parameters passed to the plugins:
string &$sEmail
string &$sPassword
JavaScript inside your plugin
Your plugin can use the function addJs
(definition) to inject JavaScript to SnappyMail. Inside this JavaScript code you could for example react on JavaScript Events.
Modify the UI of SnappyMail at runtime
SnappyMail uses templates to define how for example the SystemDropDown
menu (see image below) has to look like.
If your plugin needs to modify something inside this templates (for example add an additional button to the UI or modify the list of available functions inside the SystemDropDown
) you can add JavaScript code to your plugin that modifies this template in the right moment. Detailed information on this topic can be found here: https://github.com/the-djmaze/snappymail/issues/733#issuecomment-1333725216 .