BISO uses crunz as the central task scheduler. Instead of setting up a separate cronjob for each recurring task, only a single cronjob is required. The scheduler independently checks which tasks are due and executes them.
The task files are located in the webroot/backend/scheduler/tasks/ directory. Individual tasks
are conditionally activated depending on configuration (Config) or parameters (Admin > Parameters).
On the customer system, a single crontab entry must be set up that runs every minute:
* * * * * www-data /usr/bin/php /var/www/biso/webroot/biso-cli scheduler run
Note: The path
/var/www/biso/webroot/must be adjusted to the respective installation.
The scheduler delegates to crunz, which reads the individual task files and only executes the tasks due
at the respective time. Overlapping executions are prevented by
preventOverlapping().
| Command | Description |
|---|---|
php biso-cli scheduler list |
Show all registered tasks with schedule |
php biso-cli scheduler run |
Execute due tasks |
php biso-cli scheduler run --force |
Execute all tasks immediately (independent of schedule) |
php biso-cli scheduler run --task=N |
Execute a specific task by number |
To add a new scheduler task, create a new PHP file in the directory
webroot/backend/scheduler/tasks/. The filename must end with Tasks.php
(e.g. MyNewTasks.php).
Basic structure of a task file:
<?php
use Crunz\Schedule;
require_once __DIR__ . '/../../../components/composer/kadenpartner/gaia/bootstrap.php';
$schedule = new Schedule();
// Conditional activation (optional):
if (Param::get('mein_feature_aktiv', false)) {
$schedule->run(PHP_BINARY . ' ' . Config::get('rootdir') . '/biso-cli mein-command')
->daily()->at('08:00')
->description('Beschreibung des Tasks')
->preventOverlapping()
->appendOutputTo(Config::get('tmpdir') . '/mein-task.log');
}
return $schedule;
| Method | Description |
|---|---|
->everyMinute() |
Every minute |
->hourly() |
Hourly |
->hourlyAt('15') |
Hourly at minute 15 |
->daily() |
Daily at midnight |
->daily()->at('08:00') |
Daily at 08:00 |
->weekly() |
Weekly |
->weeklyOn(1, '13:30') |
Weekly on Monday at 13:30 (0=Sunday) |
->monthly() |
Monthly |
->cron('30 8 * * Mon,Fri') |
Arbitrary cron expression |
->preventOverlapping() to prevent parallel executions of the same task->appendOutputTo(Config::get('tmpdir') . '/taskname.log') to write output to a log file->description('...') so the task is identifiable in scheduler listEach task writes its output to its own log file in the data/tmp/ directory:
BISO sends all outgoing email via SMTP. Sending is centralized in
MailManager (and its PHPMailer
subclass BisoMailer), which is
the single place where SMTP configuration takes effect. If smtp_server is left
empty, BISO does not send mail (all callers return true without opening a
connection).
The following configuration values are set in config.php.
The five smtp_* keys configure the SMTP connection and the default sender.
The values are applied once when the PHPMailer instance is created.
// config.php
// SMTP server (hostname or IP). Empty = no mail sending.
Config::set('smtp_server', 'mail.example.com');
// SMTP authentication. Empty = open SMTP without login.
Config::set('smtp_user', '');
Config::set('smtp_pass', '');
// Additional PHPMailer options. Any PHPMailer property can be set here
// (commonly used: Port, SMTPAuth, SMTPSecure, SMTPOptions, SMTPDebug, ...).
Config::set('smtp_options', array(
'Port' => 25,
'SMTPAuth' => false,
'SMTPSecure' => false,
'SMTPOptions' => array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true,
),
),
));
// Default system sender address (From: for internally generated mails
// such as appointment invitations, cronjob notifications, CMBB-TG
// case-completion mails).
Config::set('smtp_sender', 'admin@biso.ch');
| Key | Required | Meaning |
|---|---|---|
smtp_server |
yes (to send mail) | SMTP server (hostname or IP). Empty disables mail sending entirely. |
smtp_user |
if authenticated | SMTP username. Empty for open SMTP. |
smtp_pass |
if authenticated | SMTP password. |
smtp_options |
no | Array of PHPMailer properties (e.g. Port, SMTPAuth, SMTPSecure, SMTPOptions). Each key is assigned to the mailer instance. |
smtp_sender |
recommended | Default sender for mails triggered internally by the system (cronjobs, appointment invitations, canton delegates). |
Some authenticated SMTP servers reject mails whose SMTP envelope sender
(MAIL FROM / PHPMailer property Sender) differs from the SMTP-authenticated
user's mailbox. By default, BISO uses the actual user's email address as the
envelope for every mail. The three keys below let you replace that envelope
(and optionally the visible From: header) with a fixed system-owned address.
The override is applied centrally in BisoMailer::send() and therefore affects
every mail call (the regular path through MailManager::sendEmail() and
direct callers like the VCalendar entities).
// config.php
// Replace the SMTP envelope (MAIL FROM / Sender). null/empty = no override
// (BISO uses the original per-user envelope).
Config::set('smtp_envelope_sender', 'noreply@example.com');
// Also replace the visible "From:" header with the envelope address.
// The display name (FromName) of the original sender is preserved (the
// recipient sees e.g. "John Doe <noreply@example.com>").
// false = "From:" stays the original per-user address.
Config::set('smtp_force_envelope_sender_as_from', true);
// Add the original "From:" address as "Reply-To:" so replies still reach
// the original sender. Only effective when "From:" was actually overridden.
// false = no Reply-To is added (replies go to the envelope address).
Config::set('smtp_use_from_as_replyto', true);
| Key | Default | Effect |
|---|---|---|
smtp_envelope_sender |
null |
Fixed envelope address. null disables all three overrides. |
smtp_force_envelope_sender_as_from |
false |
If true, also replace the visible From: header. |
smtp_use_from_as_replyto |
false |
If true and From: was actually overridden, add the original From: address as Reply-To:. |
Behaviour matrix:
smtp_envelope_sender |
smtp_force_envelope_sender_as_from |
smtp_use_from_as_replyto |
Result |
|---|---|---|---|
null |
any | any | No override. From: and envelope = original sender. |
| set | false |
false |
Envelope = fixed, From: = original sender. |
| set | false |
true |
Envelope = fixed, From: = original sender (Reply-To redundant). |
| set | true |
false |
Envelope and From: = fixed, display name preserved. |
| set | true |
true and original ≠ envelope |
Envelope and From: = fixed, original From: as Reply-To:. |
| set | true |
true and original = envelope |
Envelope and From: = fixed, no self-Reply-To (deduplication). |
Mail sending writes to its own logger mailer (see webroot/config.orig.php).
For targeted error diagnosis, configure a dedicated log file:
// config.php:
Config::set('gaia.logging', [
'mailer' => [
'type' => 'file', // 'file' | 'console' | 'none'
'level' => 'DEBUG', // 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR'
'console' => 'output',
'logfile' => Config::get('tmpdir') . '/biso-mailer.log',
],
]);
incamail.* configuration keys).BISO supports multiple SMS providers. The provider is selected via the sms_driver configuration option (see below). By default the historical mail-to-SMS gateway dispatch is active (sms_driver = 'email'), so existing installations continue to work without changes.
Note for existing installations: The new provider architecture is backward-compatible. If
sms_driveris not set explicitly, the behaviour is identical to the previous setup (mail gateway viasms_gateway_mail_domain/sms_gateway_mail_sender). Switch to the Swisscom REST API, the ASP SMS REST API, or the dummy driver by setting the corresponding configuration values.
| Driver | Transport | Configuration required | Use case |
|---|---|---|---|
email (default) |
SMTP mail to <mobile>@<sms_gateway_mail_domain> |
sms_gateway_mail_domain, optionally sms_gateway_mail_sender |
Existing federal SMS gateway solutions (e.g. smsc.admin.ch), customers with a mail-to-SMS bridge. |
swisscom |
HTTPS POST to Swisscom Web-to-SMS REST API | sms_swisscom_api_key (mandatory), optional sms_swisscom_api_url, sms_swisscom_sender, sms_swisscom_max_msg_parts, sms_swisscom_validity_minutes |
Cantons with a Swisscom contract requiring a pure REST integration. |
aspsms |
HTTPS POST to ASP SMS JSON API (/SendSimpleTextSMS) |
sms_aspsms_userkey (mandatory), sms_aspsms_password (mandatory), optional sms_aspsms_api_url, sms_aspsms_sender |
Customers with an ASP SMS contract (aspsms.ch) using the ASP JSON endpoint. |
dummy |
No dispatch, log entry only | – | Test and DEV environments. |
In config.php:
Config::set('sms_driver', 'email'); // default, backward-compatible
// or
Config::set('sms_driver', 'swisscom');
// or
Config::set('sms_driver', 'aspsms');
// or
Config::set('sms_driver', 'dummy'); // for tests / staging
Config::set('sms_gateway_mail_domain', 'smsc.admin.ch');
Config::set('sms_gateway_mail_sender', 'info@kunde.ch');
Config::set('sms_ausloeser_username', 'admin');
Config::set('sms_versand_iso_encoding', true); // encode body to ISO-8859-1
Notes:
sms_gateway_mail_sender = null → the berater (counselor) email is used.sms_gateway_mail_sender = '' → no sender.swisscom) always expects UTF-8. The encoding switch only affects the email driver.Implementation per the Swisscom "Web to SMS" administrator manual (September 2025). SMS dispatch is performed via HTTPS POST to the Swisscom Web-to-SMS REST endpoint. Authentication uses an API key in the HTTP header HTTP_X_APIKEY.
The API key is required (mandatory). All other values have sensible defaults per the Swisscom specification.
Config::set('sms_driver', 'swisscom');
// Mandatory: API key from the SMMS portal
Config::set('sms_swisscom_api_key', '<api-key-from-smms-portal>');
// Optional with default values:
Config::set('sms_swisscom_api_url', 'https://web2sms.swisscom.com/v8/api/rest/sms'); // default
Config::set('sms_swisscom_sender', '<originator: 1-16 numeric or 1-11 alphanumeric>');
Config::set('sms_swisscom_max_msg_parts', 5); // 1-9, default 5
Config::set('sms_swisscom_validity_minutes', 720); // 1-10080, default 720 (12h)
| Configuration value | Type | Default | Meaning |
|---|---|---|---|
sms_swisscom_api_key |
string | – | Mandatory. API key from the SMMS portal. Sent in the HTTP header HTTP_X_APIKEY. |
sms_swisscom_api_url |
string | https://web2sms.swisscom.com/v8/api/rest/sms |
REST endpoint. Usually no need to change. |
sms_swisscom_sender |
string | – | Originator per the Swisscom contract. Sent in the JSON field smsorig. |
sms_swisscom_max_msg_parts |
int | 5 |
Maximum number of SMS splits (1-9). Text exceeding this is truncated. |
sms_swisscom_validity_minutes |
int | 720 |
SMS lifetime in the SMSC in minutes (1-10080). After expiry a non-delivery report is generated. |
For each SMS, BISO sends the following JSON body to Swisscom:
{
"recipient": "+41790001122",
"msg": "<SMS body, UTF-8, max. 1377 characters>",
"smsorig": "<from sms_swisscom_sender or berater sender>",
"maxMsgParts": 5,
"validity": 720
}
BISO normalises recipient numbers from the internal 0041… format to the international E.164 format +41… required by the Swisscom API.
2xx → dispatch accepted>= 400 → Exception with HTTP code and truncated response bodyExceptionNote: Delivery reports and SMS replies are sent asynchronously by Swisscom to a response channel (URL or e-mail) configured in the SMMS portal. BISO does not consume these in this version.
Implementation per the ASPSMS JSON API. SMS dispatch is performed via HTTPS POST to the /SendSimpleTextSMS endpoint. Authentication and all SMS parameters are submitted as a JSON body (no query string).
Mandatory fields are Userkey and Password. The originator (Sender) is optional and falls back to BISO if not set.
Config::set('sms_driver', 'aspsms');
// Mandatory: API credentials from the ASP account
Config::set('sms_aspsms_userkey', '<userkey-from-aspsms-account>');
Config::set('sms_aspsms_password', '<password-from-aspsms-account>');
// Optional with default values:
Config::set('sms_aspsms_api_url', 'https://json.aspsms.com'); // base URL; default
Config::set('sms_aspsms_sender', '<originator: numeric or alphanumeric, max. 11 characters>'); // default: 'BISO'
| Configuration value | Type | Default | Meaning |
|---|---|---|---|
sms_aspsms_userkey |
string | – | Mandatory. API userkey from the ASP account. Sent as JSON field UserName. |
sms_aspsms_password |
string | – | Mandatory. API password from the ASP account. Sent as JSON field Password. |
sms_aspsms_api_url |
string | https://json.aspsms.com |
Base URL of the ASP JSON API. The suffix /SendSimpleTextSMS is appended to this URL. Usually no need to change (e.g. test tenants are possible). |
sms_aspsms_sender |
string | BISO |
Originator of the SMS (sender ID). Numeric or alphanumeric, max. 11 characters. Sent as JSON field Originator. |
For each SMS, BISO sends the following POST request to ASP:
POST https://json.aspsms.com/SendSimpleTextSMS
Content-Type: application/json; charset=utf-8
{
"UserName": "...",
"Password": "...",
"Originator": "BISO",
"Recipients": ["+41790001122"],
"MessageText": "..."
}
| JSON field | Type | Value range | Meaning |
|---|---|---|---|
UserName |
string | – | API userkey (mandatory). |
Password |
string | – | API password (mandatory). |
Originator |
string | numeric or alphanumeric, max. 11 characters | Sender ID. Default: BISO. |
Recipients |
string[] | E.164 format +41790001122 |
Recipient mobile numbers as a JSON array. BISO sends one request per number with a single array element and normalises numbers from Person::getMobileNumbers() to E.164 format +41…. |
MessageText |
string | UTF-8 | SMS body (UTF-8). |
BISO normalises recipient numbers from the internal 0041… or 079… format to E.164 format +41790001122, as shown in the ASP JSON API examples.
Success is determined solely by the HTTP status. The JSON response body ({"StatusCode": …, "StatusInfo": …}) is not evaluated.
2xx → dispatch accepted>= 400 → Exception with HTTP code and truncated response bodyExceptionConfig::set('sms_driver', 'dummy');
No additional configuration is required. Dispatch is recorded in the sms logger channel only;
no external HTTP or mail call is performed.
SMS reminders are a special case within SMS configuration: they are sent on a schedule, before an appointment, to the customer's mobile number.
The following parameters (Admin > Parameters) are available in the admin panel:
| Parameter | Label | Meaning |
|---|---|---|
sms_versand |
With appointment SMS | Appointment SMS module on/off |
sms_notification_time |
Reminder distance in hours | Defines the time distance before the appointment at which an SMS reminder is triggered |
sms_versand_manuell |
Trigger manually | SMS can be triggered manually via the appointment interface |
sms_text_max_length |
SMS text max length | Frontend textarea max length (e.g. 160 characters for one SMS). Disabled by default. |
mit_sms_suppression |
Enable SMS suppression | Activates suppression via meeting point and appointment type (default: false). |
The SMS texts themselves are no longer parameters but letter templates of type SMS (Value list > Letter templates > New > SMS template). This allows several templates per use case, distinguished by filter criteria (language, age, chargeability, counselling type, case types, regional offices).
Every SMS template has an SMS type (column brief_typ, the same one letter templates use):
| SMS type | Used for |
|---|---|
sms_termin |
Appointment reminder (cron job and manual dispatch on the BF appointment) |
sms_workshop |
Reminder for group test / workshop appointments |
sms_brief_info |
Info SMS when a letter is created (checkbox in the document dispatch) |
On dispatch the template whose set filters all match is chosen. If several match, the most specific one wins (the most matching criteria); only one SMS is ever sent. If two templates match equally many criteria, the one with a matching language filter wins, so nobody receives a text in the wrong language. Best practice is to combine overlapping criteria in a single template (e.g. regional office and language), which makes the choice unambiguous. A template without any filter acts as the default. If no template matches, no SMS is sent and the error is recorded in the SMS log.
The language is not configured on the template: an SMS is always rendered in the
recipient's language (the client's counselling language, falling back to the counsellor's,
otherwise German) — the same one the language filter checks against. This affects weekday and
month names (%A, %B) and the salutation placeholders. For a separate French text, create a
second template with the language filter "client speaks French".
The available placeholders are the same Smarty scope as for email templates ({$kunde.*},
{$berater.*}, {$termin.*}, {$institution.*}, {$treffpunkt.*}, {$beratungsfall.*},
{$anrede.*}, {$mentor.*}, plus {$workshop_plan.*} for workshops). In the form, combo
boxes insert the placeholders at the cursor position.
The former parameters
sms_versand_termin_vorlage[_fr],sms_versand_workshop_vorlage[_fr]andsms_versand_einladungsbrief_vorlage[_fr]were migrated into such templates by a DB migration. The_frvariant became a template with the language filter "client speaks French", the German one the filter-less default template.The old
paramrows are kept in the database as a backup for now, but are no longer read and no longer visible in the parameter form. Changing them has no effect — only the template matters.
So that appointment SMS mails are sent to the SMS gateway, the CLI command
biso-cli termin-sms --do-it must be executed at regular intervals.
Example Linux crontab:
0 * * * * www-data /usr/bin/php /var/www/biso/webroot/biso-cli termin-sms --do-it
Alternatively the SmsReminderTasks crunz scheduler can be enabled (see
webroot/backend/scheduler/tasks/SmsReminderTasks.php),
which invokes the same CLI command hourly.
For test dispatch in DEV/staging, use sms_driver = 'dummy' (see above).
(See above, parameter mit_sms_suppression.)
For appointments where no SMS dispatch is desired, suppression can be activated per meeting point or appointment type.
In addition two table fields control the actual behaviour:
Treffpunkt.suppress_sms (boolean): suppress SMS for appointments at this meeting point.BfTerminBesprechungsart.suppress_sms (boolean): suppress SMS for appointments with this appointment type.Suppression applies both to the automatic appointment reminder and to the manual trigger
from the appointment UI. In the brief info SMS flow (sendBriefInfo), an active suppression
silently skips the dispatch.
SMS dispatch events are recorded in a dedicated sms logger channel. By default the logs land in
data/tmp/biso-sms.log.
// config.php (see system_config.php for defaults):
Config::set('LOGGING', [
'sms' => [
'type' => 'file',
'level' => 'DEBUG',
'console' => 'output',
'logfile' => Config::get('tmpdir') . '/biso-sms.log',
],
]);
After case completion, cases can be supplied with a follow-up email. For this, the following prerequisites are necessary:
The follow-up survey is triggered via cron job, e.g. daily:
$ php webroot/biso-cli send-nachbefragung
The following configurations are relevant for follow-up surveys:
Customer-specific configs
Config::set('nachbefragung.ausnahme_termin_distanz_tage', 14): Exception: If the last appointment
is x days before case completion, no sending is triggeredThe follow-up CLI job logs its output to a separate logging target:
SendNachbefragung
The logs can therefore be directed to a dedicated logfile or to the console:
// config.php:
Config::set('LOGGING', [
'SendNachbefragung' => [
'type' => Logger::TYPE_CONSOLE,
'level' => Logger::DEBUG,
]
]);
The log system requires a second database with the Log table.
CREATE DATABASE biso_log
WITH
OWNER = bisoadm
ENCODING = 'UTF8'
LC_COLLATE = 'de_CH.utf8'
LC_CTYPE = 'de_CH.utf8'
TABLESPACE = pg_default
CONNECTION LIMIT = -1
CREATE TABLE IF NOT EXISTS log
(
id serial NOT NULL,
logtime timestamp,
context text,
benutzer text,
action text,
record_id int,
record text ,
CONSTRAINT log_pkey PRIMARY KEY (id)
)
In config.php, a second DB connection must be configured, and the log system can be activated separately for Store or Destroy actions:
Config::set('gaia.db',[
'main' => [ /* .... */],
'log' => [
'driver' => 'pdo_pgsql',
'dbname' => 'biso_log',
'host' => 'db',
'port' => 5432,
'user' => 'xxxxxx',
'password' => 'yyyyy',
'schema' => 'biso'
],
])
Config::set('LOG_STORE', false);
Config::set('LOG_DESTROY', true);
See Background Job System for documentation of the job queue system.
OIDC login configuration (Apache mod_auth_openidc, oidc.auto_provision,
oidc.sync_existing, oidc.userinfo_endpoint, oidc.roles_claim,
log channel) is documented under
OIDC Auto-Provisionierung. For the customer-specific
Geneva configuration (GINA, oidc.ge.* ID mapping) see
Kanton Genf: OIDC/GINA-Benutzerprovisionierung.