Tool-Tipp: Zeitslotmanagement mit Espanso – Schluss mit Termin-Pingpong
Hast Du schon mal darauf geachtet, wie oft Du am Tag kurz aus Deiner eigentlichen Arbeit rausgehst, nur um einen Termin zu koordinieren?
Es sind selten lange Unterbrechungen. Eher so kleine Einschübe zwischendurch. Kalender öffnen, kurz schauen, zwei oder drei Zeiten raussuchen, eine E-Mail formulieren und wieder zurück zur eigentlichen Aufgabe. Und genau diese vielen kleinen Wechsel sind es, die sich am Ende bemerkbar machen.
Mir ging es lange genauso, bis ich irgendwann gemerkt habe, dass nicht der Aufwand an sich das Problem ist, sondern die Tatsache, dass ich ihn jedes Mal neu denke.
Der Moment, in dem es „klick“ gemacht hat
Ich habe mich irgendwann gefragt, warum ich Terminabstimmungen eigentlich immer wieder von vorne durchdenke. Die Struktur ist ja fast immer gleich. Es geht darum, ein paar sinnvolle Zeitfenster vorzuschlagen, diese verständlich zu formulieren und im Idealfall sicherzustellen, dass sie auch wirklich frei bleiben.
Und genau an diesem Punkt habe ich angefangen, mir das Ganze einmal bewusst als Prozess anzuschauen, statt als spontane Aufgabe.
Ein kurzer Exkurs: Was Espanso eigentlich kann
Für die Umsetzung nutze ich Espanso. Wenn Du das Tool noch nicht kennst, lohnt sich ein Blick darauf auf jeden Fall. Im Kern ist es ein Text-Expander, also ein Werkzeug, mit dem Du durch kurze Kürzel längere Inhalte einfügen kannst.
Der spannende Teil ist jedoch, dass Espanso nicht bei statischem Text aufhört. Du kannst damit auch Skripte ausführen, Eingaben abfragen und Dir so kleine, eigene Tools bauen, die genau auf Deinen Alltag zugeschnitten sind.
In Kombination mit Espanso Dynamic Forms wird das Ganze noch interessanter, weil Du Dir damit einfache Benutzeroberflächen bauen kannst, ohne gleich eine komplette Anwendung entwickeln zu müssen.
Wenn Du dazu mehr wissen willst, schau gerne in meinen ausführlichen Artikel rein 👉 https://www.steffenbischoff.com/tool-tipp-espanso/
Wie sich der Workflow im Alltag anfühlt
In der Praxis sieht das bei mir so aus, dass ich einfach ##slots tippe und sich daraufhin ein kleines Formular öffnet. Dort trage ich meine möglichen Termine ein, wähle bei Bedarf die Sprache aus und gebe dem Ganzen einen passenden Kontext, in dem ich einen “Betreff” definiere, also wofür ich freie Terminslots anbiete.
Das dauert vielleicht eine halbe Minute, fühlt sich aber deutlich strukturierter an, als einfach in den Kalender zu schauen und „irgendwas“ zusammenzustellen.
Der eigentliche Unterschied zeigt sich aber erst danach.
Was im Hintergrund passiert

Sobald ich das Formular bestätige, übernimmt ein Skript die weitere Arbeit. Es formatiert meine Eingaben in einen Text, der durch Espanso direkt in meine E-Mail übernommen wird, ohne noch groß darüber nachdenken zu müssen, wie ich die Termine formuliere.

Parallel dazu wird eine Kalenderdatei erzeugt, die alle vorgeschlagenen Slots enthält. Diese öffnet sich automatisch, und ich kann die Termine mit einem Klick als Blocker in meinen Kalender übernehmen. Damit sind genau die Zeiten geschützt, die ich gerade angeboten habe.

Und es gibt noch einen dritten Baustein, den ich nicht mehr missen möchte: Es wird automatisch eine Aufgabe in Todoist angelegt, die mich nach drei Tagen daran erinnert, noch einmal nachzufassen, falls keine Rückmeldung gekommen ist.
Warum gerade dieser letzte Schritt so viel bringt
Wenn ich ehrlich bin, war das Nachfassen früher genau der Teil, der gerne mal untergegangen ist. Nicht bewusst, sondern einfach, weil andere Dinge dazwischenkamen.
Du kennst das vielleicht: Du gehst davon aus, dass Du Dich schon wieder melden wirst, wenn nichts passiert. Und dann vergeht Zeit, die Mail rutscht nach unten und irgendwann ist das Thema aus dem Blick.
Durch die automatische Aufgabe passiert genau das nicht mehr. Der Follow-up ist kein loses Versprechen mehr an Dich selbst, sondern ein klar definierter Schritt in Deinem System.
Was sich dadurch verändert hat
Seit ich diesen Workflow nutze, denke ich über Terminabstimmungen kaum noch nach. Sie sind einfach ein fester Ablauf geworden, der im Hintergrund funktioniert.
Ich verliere weniger Fokus, weil ich nicht ständig zwischen Aufgaben springen muss. Gleichzeitig habe ich mehr Sicherheit, weil ich weiß, dass weder meine Slots verschwinden noch ich das Nachfassen vergesse.
Es ist kein riesiger Unterschied auf den ersten Blick, aber im Alltag macht er sich deutlich bemerkbar.
So setzt Du das für Dich um
Du musst nicht exakt meine Lösung kopieren. Aber das Prinzip kannst Du gerne adaptieren. Und auch meine konkreten Scripte stelle ich dir natürlich gerne zur Verfügung.
Schritt 1: Espanso installieren
Falls Du es noch nicht nutzt: Tu es. Es ist eines der unterschätztesten Tools für Produktivität.
Schritt 2: Trigger definieren
Lege in Deiner Konfiguration einen Trigger wie z. B. ##slots an.
# espanso match file
# For a complete introduction, visit the official docs at: https://espanso.org/docs/
# You can use this file to define the base matches (aka snippets)
# that will be available in every application when using espanso.
# Matches are substitution rules: when you type the "trigger" string
# it gets replaced by the "replace" string.
matches:
# Meeting Slots Picker
- trigger: "##slots"
replace: "{{output}}"
label: "Meeting Slots Picker"
vars:
- name: output
type: script
params:
args:
- python
- "%CONFIG%\\scripts\\process_slots.py"
- "%CONFIG%\\config\\forms\\slots_form.yml"
Schritt 3: Formular bauen
Über EDF definierst Du:
- Eingabefelder für Datum und Uhrzeit
- Die Sprache, in der die Zeitslots ausgegeben werden sollen
- Sonstige Standardwerte
Halte es so einfach wie möglich.
schema:
type: object
properties:
lang:
type: string
title: "Sprache / Language"
enum:
- "Deutsch"
- "English"
subject:
type: string
title: "Betreff"
d1:
type: string
s1:
type: string
e1:
type: string
d2:
type: string
s2:
type: string
e2:
type: string
d3:
type: string
s3:
type: string
e3:
type: string
d4:
type: string
s4:
type: string
e4:
type: string
d5:
type: string
s5:
type: string
e5:
type: string
data:
lang: "Deutsch"
subject: "🛑 BLOCKER: "
uischema:
type: VerticalLayout
elements:
- type: Control
scope: "#/properties/lang"
label: "Sprache / Language"
options:
format: radio
vuetify:
v-radio-group:
inline: true
hideDetails: true
- type: Control
scope: "#/properties/subject"
label: "Betreff"
- type: Group
label: "Slot 1"
elements:
- type: HorizontalLayout
elements:
- type: Control
scope: "#/properties/d1"
label: "Datum"
options:
format: date
dateFormat: DD.MM.YYYY
dateSaveFormat: YYYY-MM-DD
- type: Control
scope: "#/properties/s1"
label: "Start"
options:
format: time
timeFormat: "HH:mm"
timeSaveFormat: "HH:mm"
- type: Control
scope: "#/properties/e1"
label: "Ende"
options:
format: time
timeFormat: "HH:mm"
timeSaveFormat: "HH:mm"
- type: Group
label: "Slot 2"
elements:
- type: HorizontalLayout
elements:
- type: Control
scope: "#/properties/d2"
label: "Datum"
options:
format: date
dateFormat: DD.MM.YYYY
dateSaveFormat: YYYY-MM-DD
- type: Control
scope: "#/properties/s2"
label: "Start"
options:
format: time
timeFormat: "HH:mm"
timeSaveFormat: "HH:mm"
- type: Control
scope: "#/properties/e2"
label: "Ende"
options:
format: time
timeFormat: "HH:mm"
timeSaveFormat: "HH:mm"
- type: Group
label: "Slot 3"
elements:
- type: HorizontalLayout
elements:
- type: Control
scope: "#/properties/d3"
label: "Datum"
options:
format: date
dateFormat: DD.MM.YYYY
dateSaveFormat: YYYY-MM-DD
- type: Control
scope: "#/properties/s3"
label: "Start"
options:
format: time
timeFormat: "HH:mm"
timeSaveFormat: "HH:mm"
- type: Control
scope: "#/properties/e3"
label: "Ende"
options:
format: time
timeFormat: "HH:mm"
timeSaveFormat: "HH:mm"
- type: Group
label: "Slot 4"
elements:
- type: HorizontalLayout
elements:
- type: Control
scope: "#/properties/d4"
label: "Datum"
options:
format: date
dateFormat: DD.MM.YYYY
dateSaveFormat: YYYY-MM-DD
- type: Control
scope: "#/properties/s4"
label: "Start"
options:
format: time
timeFormat: "HH:mm"
timeSaveFormat: "HH:mm"
- type: Control
scope: "#/properties/e4"
label: "Ende"
options:
format: time
timeFormat: "HH:mm"
timeSaveFormat: "HH:mm"
- type: Group
label: "Slot 5"
elements:
- type: HorizontalLayout
elements:
- type: Control
scope: "#/properties/d5"
label: "Datum"
options:
format: date
dateFormat: DD.MM.YYYY
dateSaveFormat: YYYY-MM-DD
- type: Control
scope: "#/properties/s5"
label: "Start"
options:
format: time
timeFormat: "HH:mm"
timeSaveFormat: "HH:mm"
- type: Control
scope: "#/properties/e5"
label: "Ende"
options:
format: time
timeFormat: "HH:mm"
timeSaveFormat: "HH:mm"
Schritt 4: Skript anpassen
Hier passiert die Individualisierung.
Du kannst z. B.:
- eigene Textbausteine definieren
- andere Formulierungen nutzen
- zusätzliche Infos einbauen
Wichtig ist nur: Das Skript soll am Ende Text ausgeben, die .ics Datei erzeugen und in meinem Fall eine Todoist-Aufgabe über die API erstellen.
import sys
import json
import os
import tempfile
import subprocess
from datetime import datetime, timedelta
import urllib.request
import urllib.error
# Wir stellen sicher, dass die Ein- und Ausgabe auf UTF-8 läuft
if sys.stdout.encoding != 'utf-8':
sys.stdout.reconfigure(encoding='utf-8')
if sys.stdin.encoding != 'utf-8':
sys.stdin.reconfigure(encoding='utf-8')
L10N = {
"Deutsch": {
"days": ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"],
"months": ["", "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"],
"template": "{wd}, {d}. {m} zwischen {start} und {end} Uhr"
},
"English": {
"days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
"months": ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
"template": "{wd}, {m} {d} between {start} and {end}"
}
}
def create_and_open_ics(slots, subject):
ics_lines = [
"BEGIN:VCALENDAR",
"VERSION:2.0",
"PRODID:-//Espanso//EDF//EN",
"METHOD:PUBLISH",
"X-MS-OLK-FORCEINSPECTOROPEN:TRUE" # Hilft Outlook, das Fenster direkt zu öffnen
]
for s in slots:
d_clean = s['date'].replace("-", "")
s_clean = s['start'].replace(":", "")
e_clean = s['end'].replace(":", "")
ics_lines += [
"BEGIN:VEVENT",
f"SUMMARY:{subject}",
f"DTSTART:{d_clean}T{s_clean}00",
f"DTEND:{d_clean}T{e_clean}00",
"TRANSP:OPAQUE", # Markiert die Zeit als "Beschäftigt"
"END:VEVENT"
]
ics_lines.append("END:VCALENDAR")
# Datei auf dem Desktop speichern, damit sie manuell geöffnet werden kann, falls der Autostart fehlschlägt
desktop_path = os.path.join(os.path.expanduser("~"), "Desktop", "Terminblocker.ics")
with open(desktop_path, "w", encoding="utf-8") as f:
f.write("\n".join(ics_lines))
# Versuche, die Datei direkt zu öffnen (ohne Umweg über einen Hintergrundprozess)
try:
os.startfile(desktop_path)
except Exception:
pass
def create_todoist_task(subject, output_lines):
api_token = os.environ.get("TODOIST_API_TOKEN")
if not api_token:
return
url = "https://api.todoist.com/api/v1/tasks"
current_date = datetime.now()
added_days = 0
while added_days < 3:
current_date += timedelta(days=1)
if current_date.weekday() < 5: # 0-4 entspricht Montag-Freitag
added_days += 1
due_date = current_date.strftime("%Y-%m-%d")
description = "Vorgeschlagene Termine:\n" + "\n".join(f"- {line}" for line in output_lines)
data = {
"content": f"Nachfassen: {subject}",
"description": description,
"project_id": "6CrfghjghcrMMmc8",
"labels": ["⌛ Warten auf"],
"due_date": due_date
}
req = urllib.request.Request(url, data=json.dumps(data).encode('utf-8'))
req.add_header('Content-Type', 'application/json')
req.add_header('Authorization', f'Bearer {api_token}')
try:
with urllib.request.urlopen(req) as response:
pass
except Exception:
pass
def run_edf(config_path):
edf_path = r"C:\Users\steff\AppData\Local\Programs\Espanso Dynamic Forms\EDF.exe"
# Execute EDF and capture its JSON output
result = subprocess.run([edf_path, "--", "--form-config", config_path], capture_output=True, text=True, encoding='utf-8')
if result.returncode == 0:
return result.stdout
return None
def main():
try:
if len(sys.argv) > 1:
config_path = sys.argv[1]
input_data = run_edf(config_path)
else:
input_data = sys.stdin.read()
if not input_data:
return
data = json.loads(input_data)
except Exception as e:
# Falls Fehler passieren, geben wir sie nicht an Espanso aus, um den Textfluss nicht zu stören
return
lang = data.get("lang", "Deutsch")
subject = data.get("subject", "Block: Terminvorschlag")
conf = L10N.get(lang, L10N["Deutsch"])
valid_slots = []
output_lines = []
for i in range(1, 6):
d = data.get(f"d{i}")
s = data.get(f"s{i}")
e = data.get(f"e{i}")
if d and s and e:
try:
# Bereinige die Zeitstrings (z.B. "14:00:00+02:00" -> "14:00")
s = s.split('+')[0].split('-')[0]
if len(s.split(':')) >= 2:
s = ":".join(s.split(':')[:2])
e = e.split('+')[0].split('-')[0]
if len(e.split(':')) >= 2:
e = ":".join(e.split(':')[:2])
dt = datetime.strptime(d, "%Y-%m-%d")
valid_slots.append({"date": d, "start": s, "end": e})
formatted = conf["template"].format(
wd=conf["days"][dt.weekday()],
d=dt.day,
m=conf["months"][dt.month],
start=s,
end=e
)
output_lines.append(formatted)
except:
continue
if not output_lines:
return
# Text-Ausgabe (wird von Espanso eingefügt)
print("\n".join(output_lines))
# ICS im Hintergrund starten
create_and_open_ics(valid_slots, subject)
# Todoist-Aufgabe anlegen
create_todoist_task(subject, output_lines)
if __name__ == "__main__":
main()
Schritt 5: Workflow testen
Der entscheidende Moment:
Tippe ##slots und prüfe:
- Funktioniert das Formular?
- Ist der Text korrekt?
- Werden die Kalender-Blocker erstellt?
Wenn das sitzt, hast Du einen Prozess gebaut, der Dich täglich unterstützt.
Vielleicht auch ein Ansatz für Dich
Du musst das nicht exakt so umsetzen wie ich. Aber vielleicht nimmst Du die Idee mit, wiederkehrende Aufgaben einmal bewusst zu strukturieren und Dir zu überlegen, welche Teile davon sich automatisieren lassen.
Gerade Dinge, die häufig passieren und immer ähnlich ablaufen, sind perfekte Kandidaten dafür. Und Terminabstimmung gehört ziemlich sicher dazu.
Wie gehst Du aktuell mit solchen kleinen, aber wiederkehrenden Aufgaben um? Hast Du dafür schon feste Abläufe oder entsteht vieles noch spontan?
Ich finde es immer spannend zu sehen, wie unterschiedlich solche Dinge gelöst werden.
Und denke dran: Bleib produktiv!