A Python service that connects to your Exchange on-premise server, fetches calendar events, and serves them as an ICS feed via HTTP.
- All-day Events: Properly handles all-day events
- Recurring Events: Converts Exchange recurrence patterns to iCalendar RRULE format
- Timezone Support: Maintains timezone information for accurate event times
- Configurable URL Paths: Customize the ICS feed URL path to your preference
- Auto-sync: Periodically syncs calendar data in the background
- Docker Ready: Easy deployment via Docker Compose
- Python 3.13+
- Docker and Docker Compose (for containerized deployment)
- Access to an Exchange on-premise server
- Valid Exchange credentials
-
Clone the repository
git clone https://github.com/thielem/exchange-ics-sync cd exchange-ics-sync -
Configure the service
Token Generate a token using e.g.
openssl rand -base64 64 | tr '+/' '-_' | tr -d '=' | tr -d '\n'.The token can be any random string and is meant to keep your calendar deployment secure. It must be passed as a URL query parameter. Any request without a valid token will return
404.config.yaml Create and edit
config.yamlwith your Exchange server details:exchange: server: "mail.example.com" email: "user@example.com" username: "username" password: "your-password" auth_type: "NTLM" calendar: name: "my-calendar" days_past: 30 days_future: 365 sync_interval_minutes: 15 server: host: "0.0.0.0" port: 8080 calendar_url_path: "/cal/{calendar_name}.ics" token: "my-secure-token"
If you wish to store secrets in environment variables instead, just configure an empty string in the yaml file. Environment variables will overwrite the
config.yamlvalues. -
Start the service
docker-compose up -d
-
Access your calendar
The ICS feed will be available at:
http://localhost:8080/cal/my-calendar.ics?token=my-secure-token
The main configuration file with the following sections:
server: Exchange server address (e.g., mail.example.com)email: Email address of the calendar accountusername: Username for authenticationpassword: Password for authenticationauth_type: Authentication type (NTLM, Basic, or Digest)
name: Calendar identifier (check your Outlook frontend)days_past: Number of days in the past to fetch events (default: 30)days_future: Number of days in the future to fetch events (default: 365)sync_interval_minutes: How often to sync with Exchange (default: 15)
host: Host to bind to (use 0.0.0.0 for all interfaces)port: Port to listen on (default: 8080)calendar_url_path: URL path pattern for the ICS feed- Use
{calendar_name}as a placeholder for the calendar name - Examples:
/cal/{calendar_name}.ics→http://server:port/cal/my-calendar.ics/{calendar_name}.ics→http://server:port/my-calendar.ics/random-string.ics->http://server:port/random-string.ics. This is recommended for security purposes.
- Use
token: URL query parameter that is used to authenticate requests to the calendar.secure_healthcheck: Whether the token must be passed as a Bearer when calling/health. This is recommended to avoid information leakage via the health endpoint. If enabled, any request with invalid token will return404.
You can override configuration values using environment variables:
EXCHANGE_SERVEREXCHANGE_EMAILEXCHANGE_USERNAMEEXCHANGE_PASSWORDEXCHANGE_DOMAINSERVER_HOSTSERVER_PORTCONFIG_PATH
To use environment variables with Docker Compose, uncomment and set them in docker-compose.yml or specify an env_file.
-
Install dependencies
uv sync
-
Configure the service
Edit
config.yamlwith your settings. -
Run the service
uv run app.py
Health check endpoint for monitoring.
If secure_healthcheck == True: Include Headers: Authorization: Bearer my-secure-token
Response:
{
"status": "healthy",
"last_sync": "2024-01-15T10:30:00+00:00"
}Serves the ICS calendar file (path configurable via calendar_url_path).
Response: ICS file with calendar events
- Open Calendar app
- File → New Calendar Subscription
- Enter the calendar URL:
http://your-server:8080/cal/my-calendar.ics?token=my-secure-token - Set refresh frequency to match your sync interval
- Open Google Calendar
- Click "+" next to "Other calendars"
- Select "From URL"
- Enter the calendar URL:
http://your-server:8080/cal/my-calendar.ics?token=my-secure-token
- Open Outlook
- File → Account Settings → Internet Calendars
- Click "New"
- Enter the calendar URL:
http://your-server:8080/cal/my-calendar.ics?token=my-secure-token
If you can't connect to Exchange:
- Verify server address and credentials in
config.yaml - Check if the Exchange server is accessible from your network
- Try different authentication types (NTLM, Basic, Digest)
- Check the logs:
docker-compose logs -f
- Check the
/healthendpoint for last sync time - Review logs for error messages
- Verify Exchange credentials have calendar access permissions
- Ensure firewall rules allow outbound connections to Exchange server
- Verify date range settings (
days_pastanddays_future) - Check if the Exchange calendar actually has events in the date range
- Review logs for filtering or fetch errors
- Security: Use a secure token and consider using a random string for the calendar name as well.
- HTTPS: Use a reverse proxy (nginx, Traefik) to add HTTPS for production.
- Network Access: Restrict access to the service using firewall rules where feasible.
MIT License - feel free to use and modify as needed.
Contributions are welcome! Please feel free to submit a Pull Request.
For issues and questions, please open an issue on the GitHub repository.