CHIME Heralds Imminent Modeline Events
Features | Installation | Quick Start | Configuration | Integrations | Troubleshooting | Development & Testing | History
" I love deadlines. I love the whooshing noise they make as they go by. " — Douglas Adams
CHIME (backronym: CHIME Heralds Imminent Modeline Events) makes sure your org-agenda events don't whoosh by unnoticed. You get desktop notifications, an audible chime, and your next event shows up in the modeline.
Features
- Visual notifications with customizable alert times
- Audible chime sound when notifications are displayed (bundled WAV or custom file)
- Customizable notification icon for desktop notifications
- Interactive modeline display of next upcoming event, highly configurable:
- Enable/disable modeline modifications
- Hover tooltip showing all upcoming events grouped by day
- Click to jump directly to event's org entry
- Customize notification text format (show/hide time, countdown, or title)
- Choose 12-hour or 24-hour time display
- Customize time-until format (verbose or compact)
- Configurable lookahead window and tooltip event limit
- Multiple notification times per event (e.g., 5 minutes before and at event time)
- Works with SCHEDULED and DEADLINE and just plain ol' regular timestamps
- Supports repeating timestamps (
+1w,.+1d,++1w) - Async background checking (runs every minute)
- All-day event support (birthdays, holidays, multi-day events)
- Configurable daily notification times for all-day events
- Advance notice (e.g., "Blake's birthday is tomorrow")
- Show/hide all-day events in tooltip
- Configurable notification filtering by keywords, tags, and custom predicates
- Configurable startup delay for async org-agenda-files initialization
- Well-tested, including with org-gcal
Installation
Not on MELPA yet, but coming soon.
Requirements: Emacs 27.1+, org-mode 9.0+, and the alert, dash, and async packages. Package managers pull these automatically.
package-vc-install (Emacs 29+)
The quickest way to try it out:
(unless (package-installed-p 'chime)
(package-vc-install "https://github.com/cjennings/chime"))
use-package with :vc (Emacs 29+)
(use-package chime
:vc (:url "https://github.com/cjennings/chime" :rev :newest)
:after alert
:commands (chime-mode chime-check)
:bind ("C-c A" . chime-check)
:config
;; Alert intervals: (minutes . severity) pairs
;; Notify 5 minutes before (medium urgency) and at event time (high urgency)
(setq chime-alert-intervals '((5 . medium) (0 . high)))
;; Chime sound
(setq chime-play-sound t)
;; Modeline display (see "Modeline Display" section for more options)
(setq chime-enable-modeline t)
(setq chime-modeline-lookahead-minutes 120)
(setq chime-modeline-format " ⏰ %s")
;; Notification settings
(setq chime-notification-title "Reminder")
;; Don't filter by TODO keywords - notify for all events
(setq chime-keyword-whitelist nil)
(setq chime-keyword-blacklist nil)
;; Only notify for non-done items
(setq chime-predicate-blacklist
'(chime-done-keywords-predicate))
;; Enable chime-mode automatically
(chime-mode 1))
straight.el
If you're using straight, you probably don't need me to tell you what to do, but here it is anyway:
(straight-use-package
'(chime :type git :host github :repo "cjennings/chime"))
quelpa
(quelpa '(chime :fetcher github :repo "cjennings/chime"))
Manual installation
git clone https://github.com/cjennings/chime.git ~/path/to/chimeThen add the following to your init file:
(add-to-list 'load-path "~/path/to/chime")
(require 'chime)
(chime-mode 1)
Restart Emacs, or evaluate those three lines with M-x eval-buffer to start chime immediately.
Quick Start
Once you've installed chime using one of the methods above, the only thing you actually need is:
(chime-mode 1)
That's it. The defaults are sensible: chime checks your org-agenda-files every 60 seconds, notifies you 10 minutes before events (medium urgency) and again at event time (high urgency), plays the bundled chime sound, and shows your next event in the modeline with a ⏰ icon. Make sure your calendar org files are in org-agenda-files — no agenda files, no notifications.
Once chime-mode is on, a ⏰ icon appears in your modeline followed by your next upcoming event. Here's how to interact with it:
- Hover over the modeline text to see a tooltip listing all upcoming events, grouped by day
- Left-click opens your calendar in a browser (if you've set
chime-calendar-url) - Right-click jumps to the next event's entry in its org file
When an event is approaching, you'll get a desktop notification and an audible chime. Everything — alert timing, sounds, modeline format, filtering — is configurable in the Configuration section below.
Manual Refresh Commands
If you've just added or changed events and don't want to wait for the next polling cycle:
M-x chime-check
To update the modeline display without sending notifications (useful after syncing your calendar with org-gcal):
M-x chime-refresh-modeline
Configuration Options
Polling Interval
Control how often chime checks for upcoming events:
;; Default: check every 60 seconds (1 minute) - recommended for most users
(setq chime-check-interval 60)
;; More responsive: check every 30 seconds
(setq chime-check-interval 30)
Lower values make notifications more responsive but increase system load. Higher values reduce polling overhead but may delay notifications slightly.
Choosing a polling interval:
- 120-300 seconds (2-5 minutes): For the "I'll get to it eventually" crowd.
- 60 seconds (default): The sweet spot. Org timestamps are minute-granularity anyway.
- 30 seconds: For the anxious. Negligible resource cost, negligible benefit.
- 10-15 seconds: You will NOT be 50 seconds more prepared for your meeting.
- Below 10 seconds: Your laptop fan will remind you of upcoming events before chime does.
Note: Changes take effect after restarting chime-mode (M-x chime-mode twice, or restart Emacs).
Alert Intervals
Set when to receive notifications and their urgency levels using (minutes . severity) pairs:
;; Default: 10 minutes before (medium) and at event time (high)
(setq chime-alert-intervals '((10 . medium) (0 . high)))
;; Single notification 10 minutes before
(setq chime-alert-intervals '((10 . medium)))
;; Multiple notifications with escalating urgency
(setq chime-alert-intervals '((10 . low) ;; 10 min before: low urgency
(5 . medium) ;; 5 min before: medium urgency
(0 . high))) ;; At event time: high urgency
Severity levels (high, medium, low) are passed to alert.el, which maps them to your notification daemon's urgency levels. What that looks like depends on your setup — dunst shows high notifications in red and keeps them on screen until dismissed, while low ones fade after a timeout. On macOS, the distinction is subtler (sound vs no sound). The exact behavior is up to your alert style and daemon config.
Chime Sound
Control the audible chime that plays when notifications appear:
;; Enable/disable the bundled chime sound (default: t)
(setq chime-play-sound t)
;; Use your own custom sound file
(setq chime-sound-file "/path/to/your/chime.wav")
;; (setq chime-sound-file "~/Music/in-a-gadda-da-vida-full-17-minutes.wav") ; don't
;; Disable sound completely (no sound file = no beep)
(setq chime-sound-file nil)
A bundled chime.wav is included (GPL-licensed). Swap in your own WAV if you prefer — Freesound (CC-licensed samples, filter by license) and Sonniss GDC (royalty-free annual packs) are good places to find short notification sounds. Chime uses Emacs's built-in play-sound-file, which supports WAV and AU formats. MP3, OGG, and other formats won't work.
Notification Icon
Set a custom icon for desktop notifications:
;; Use a custom notification icon
(setq chime-notification-icon "/path/to/icon.png")
;; No custom icon (default: nil, uses system default)
(setq chime-notification-icon nil)
The icon is passed straight to your notification daemon via alert.el. PNG is the safest choice. Most Linux daemons (dunst, mako, swaync) accept PNG, SVG, and anything GdkPixbuf can load. macOS uses its own icon handling. There are no strict size requirements, but 48x48 to 256x256 pixels is the sweet spot — anything larger gets scaled down anyway.
Startup Delay
Control how long chime waits before the first event check after enabling chime-mode. This allows org-agenda-files and related infrastructure to finish loading:
;; Default: wait 10 seconds before first check
(setq chime-startup-delay 10)
;; Faster startup (if you know org is ready)
(setq chime-startup-delay 5)
;; Increase if you always see "found 0 events" message on startup, then it corrects itself later
(setq chime-startup-delay 20)
Modeline Display
Display your next upcoming event in your modeline:
;; Enable/disable modeline display (default: t)
(setq chime-enable-modeline t)
;; Show events up to 2 hours ahead (default: 120)
(setq chime-modeline-lookahead-minutes 120)
;; Customize the modeline prefix format (default: " ⏰ %s")
(setq chime-modeline-format " [Next: %s]")
The modeline will display the soonest event within the lookahead window, formatted as:
- Default:
⏰ Argue About Tabs vs Spaces at 02:30 PM (in 15 minutes) - Updates automatically every minute
Minor mode lighter
By default, the minor mode lighter is empty — the
⏰event display already tells you chime is running. If you want a separate indicator in the minor modes list:;; Show "Chime" in the minor modes list (setq chime-modeline-lighter " Chime") ;; Or an emoji (setq chime-modeline-lighter " 🔔")No-Events Text
Control what appears in the modeline when no events are within the lookahead window:
;; Default: alarm icon (setq chime-modeline-no-events-text " ⏰") ;; Muted bell (setq chime-modeline-no-events-text " 🔕") ;; Show nothing (clean modeline) (setq chime-modeline-no-events-text nil) ;; Custom text (setq chime-modeline-no-events-text " No events")This only applies when events exist beyond the lookahead window. If there are no events at all, the modeline is always empty.
Interactive Modeline Features
The modeline text is interactive. Left-click opens your calendar in a browser, right-click jumps to the event in its org file, and hovering shows a tooltip with all upcoming events.
Tooltip
Hover your mouse over the modeline event to see a tooltip showing all upcoming events within the lookahead window, grouped by day:
Upcoming Events as of Mon Oct 28 2024 @ 02:00 PM Today, Oct 28: ───────────── Argue About Tabs vs Spaces at 02:10 PM (in 10 minutes) Explain Why You Used goto at 02:30 PM (in 30 minutes) Find Out Who Keeps Stealing Your Mug at 02:45 PM (in 45 minutes) Tomorrow, Oct 29: ───────────── Sprint Planning (Again) at 09:00 AM (tomorrow) Quarterly Review (Bring Snacks) at 02:00 PM (tomorrow)The tooltip displays up to 5 events by default. Configure the maximum with:
;; Show up to 10 events in tooltip (setq chime-modeline-tooltip-max-events 10) ;; Show all events in lookahead window (beware -- no limit!) (setq chime-modeline-tooltip-max-events nil)Customize the tooltip header format:
;; Default: "Upcoming Events as of Tue Nov 04 2025 @ 08:25 PM" (setq chime-tooltip-header-format "Upcoming Events as of %a %b %d %Y @ %I:%M %p") ;; 24-hour time (setq chime-tooltip-header-format "Upcoming Events as of %a %b %d %Y @ %H:%M") ;; Minimal header (setq chime-tooltip-header-format "Events — %a %b %d")Uses
format-time-stringcodes (%aweekday,%bmonth,%dday,%Yyear,%I12-hour,%H24-hour,%Mminutes,%pAM/PM). See the Emacs manual on time parsing for the full list.Tooltip Lookahead Window
The tooltip can show events beyond the modeline lookahead window. By default, it shows events up to 1 week (168 hours) in the future, while the modeline only shows events within the next hour:
;; Modeline shows events within next 2 hours (default: 120) (setq chime-modeline-lookahead-minutes 120) ;; Tooltip shows events within next 168 hours / 1 week (default) (setq chime-tooltip-lookahead-hours 168) ;; Example: Show only today's events in tooltip (24 hours) (setq chime-tooltip-lookahead-hours 24) ;; Example: Show events for the next month (setq chime-tooltip-lookahead-hours 720) ; 30 days × 24 hours ;; Example: Show all events for the next year (may slow checks for large org collections) (setq chime-tooltip-lookahead-hours 8760)Note: larger values increase the agenda span fetched by the async subprocess, which can slow event checks if you have many org files.
This way the modeline stays focused on what's imminent, while the tooltip gives you a wider view of what's coming up.
Click Actions
The modeline supports two click actions:
- Left-click: Opens your calendar in a web browser (if configured)
- Right-click: Jumps directly to the event's org entry in its file
To enable left-click calendar access, set your calendar URL:
;; Open Google Calendar on left-click (setq chime-calendar-url "https://calendar.google.com") ;; Or Outlook Calendar (setq chime-calendar-url "https://outlook.office.com/calendar") ;; Or any custom calendar web interface (setq chime-calendar-url "https://your-calendar-url")When no calendar URL is set (default), left-click does nothing. Right-click always jumps to the next event in your org file.
Customizing Modeline Content
Control what information appears in the modeline with fine-grained formatting:
Notification Text Format
Customize which components are shown:
;; Default: title, time, and countdown (setq chime-notification-text-format "%t at %T (%u)") ;; → "Argue About Tabs vs Spaces at 02:30 PM (in 15 minutes)" ;; Title and time only (no countdown) (setq chime-notification-text-format "%t at %T") ;; → "Argue About Tabs vs Spaces at 02:30 PM" ;; Title and countdown only (no time) (setq chime-notification-text-format "%t (%u)") ;; → "Argue About Tabs vs Spaces (in 15 minutes)" ;; Title only (minimal) (setq chime-notification-text-format "%t") ;; → "Argue About Tabs vs Spaces" ;; Custom separator (setq chime-notification-text-format "%t - %T") ;; → "Argue About Tabs vs Spaces - 02:30 PM" ;; Time first (setq chime-notification-text-format "%T: %t") ;; → "02:30 PM: Argue About Tabs vs Spaces"Available placeholders:
%t- Event title%T- Event time (formatted perchime-display-time-format-string)%u- Time until event (formatted perchime-time-left-format-*)
Event Time Format
Choose between 12-hour and 24-hour time display:
;; 12-hour with AM/PM (default) (setq chime-display-time-format-string "%I:%M %p") ;; → "02:30 PM" ;; 24-hour format (setq chime-display-time-format-string "%H:%M") ;; → "14:30" ;; 12-hour without space before AM/PM (setq chime-display-time-format-string "%I:%M%p") ;; → "02:30PM" ;; 12-hour with lowercase am/pm (setq chime-display-time-format-string "%I:%M %P") ;; → "02:30 pm"Available format codes:
%I- Hour (01-12, 12-hour format)%H- Hour (00-23, 24-hour format)%M- Minutes (00-59)%p- AM/PM (uppercase)%P- am/pm (lowercase)
Time-Until Format
Customize how the countdown is displayed:
;; Default: verbose format (setq chime-time-left-format-short "in %M") ; Under 1 hour (setq chime-time-left-format-long "in %H %M") ; 1 hour or more ;; → "in 10 minutes" or "in 1 hour 30 minutes" ;; Compact format (setq chime-time-left-format-short "in %mm") (setq chime-time-left-format-long "in %hh %mm") ;; → "in 10m" or "in 1h 30m" ;; Very compact (no prefix) (setq chime-time-left-format-short "%mm") (setq chime-time-left-format-long "%hh%mm") ;; → "10m" or "1h30m" ;; Custom "at event time" message (setq chime-time-left-format-at-event "NOW!") ;; → "NOW!" instead of "right now"Available format codes (from
format-seconds):%h/%H- Hours (number only / with unit name)%m/%M- Minutes (number only / with unit name)
Title Truncation
Limit the length of long event titles to conserve modeline space:
;; No truncation - show full title (default) (setq chime-max-title-length nil) ;; → " ⏰ Retrospective on Why the Last Retrospective Failed ( in 10m)" ;; Truncate to 25 characters (setq chime-max-title-length 25) ;; → " ⏰ Retrospective on Why... ( in 10m)" ;; Truncate to 15 characters (setq chime-max-title-length 15) ;; → " ⏰ Retrospect... ( in 10m)"Important: This setting affects only the event title (%t), not the icon, time, or countdown. The icon comes from
chime-modeline-formatand is added separately.The truncation includes the "…" in the character count, so a 15-character limit means up to 12 characters of title plus "…".
Minimum recommended value: 10 characters.
Complete Compact Example
For maximum modeline space savings:
(setq chime-enable-modeline t) (setq chime-modeline-lookahead-minutes 60) (setq chime-modeline-format " ⏰ %s") ; Minimal prefix (setq chime-notification-text-format "%t (%u)") ; No time shown (setq chime-time-left-format-short "%mm") ; Compact short (setq chime-time-left-format-long "%hh%mm") ; Compact long (setq chime-max-title-length 20) ; Truncate long titles ;; Result: "⏰ Dentist (10m)" or "⏰ Retrospective o... (1h30m)"Disabling Modeline Display
;; Completely disable modeline modifications (setq chime-enable-modeline nil) ;; Alternative: set lookahead to 0 (legacy method) (setq chime-modeline-lookahead-minutes 0)
Notification Settings
;; Notification title
(setq chime-notification-title "Reminder")
;; Note: Severity is now configured per-interval in chime-alert-intervals
;; See "Alert Intervals" section above
Filtering
;; Only notify for specific TODO keywords
(setq chime-keyword-whitelist '("TODO" "NEXT"))
;; Never notify for these keywords
(setq chime-keyword-blacklist '("DONE" "CANCELLED"))
;; Only notify for specific org tags
(setq chime-tags-whitelist '("important"))
;; Never notify for these org-tags
(setq chime-tags-blacklist '("someday"))
Whitelist and Blacklist Precedence
If the same keyword or tag appears in both a whitelist and blacklist, the blacklist wins and the item is filtered out.
Examples:
- Item with
TODOkeyword whenTODOis in bothchime-keyword-whitelistandchime-keyword-blacklist→ filtered out (blacklist wins) - Item with
:urgent:tag whenurgentis in bothchime-tags-whitelistandchime-tags-blacklist→ filtered out (blacklist wins) - Item with whitelisted keyword but blacklisted tag → filtered out (blacklist wins)
Most users configure either whitelists or blacklists, not both. If you use both, ensure they don't overlap to avoid confusion.
- Item with
Custom Predicate Filtering
Keywords and tags cover most filtering needs, but sometimes you want logic they can't express — like silencing work events on weekends, or only getting notified about events in a specific file.
For filtering logic that goes beyond keywords and tags, you can write custom predicate functions. Each predicate receives an org marker (POM) and should return non-nil to match. Whitelisted predicates include events that match; blacklisted predicates exclude them.
;; Whitelist: only notify for events in work.org (defun my-work-file-predicate (marker) "Match events that live in work.org." (string-match-p "work\\.org" (or (buffer-file-name (marker-buffer marker)) ""))) (setq chime-predicate-whitelist '(my-work-file-predicate)) ;; Blacklist: silence work events on weekends (defun my-weekend-work-silencer (marker) "Match work.org events on Saturday or Sunday." (and (memq (nth 6 (decode-time)) '(0 6)) (string-match-p "work\\.org" (or (buffer-file-name (marker-buffer marker)) "")))) ;; Blacklist: exclude events with a NO_NOTIFY property (defun my-no-notify-predicate (marker) "Match events with a NO_NOTIFY property set." (org-entry-get marker "NO_NOTIFY")) ;; Whitelist: only notify for high-priority items (defun my-priority-a-only (marker) "Match events with a [#A] priority cookie." (string= (org-entry-get marker "PRIORITY") "A")) (setq chime-predicate-blacklist '(chime-done-keywords-predicate my-weekend-work-silencer my-no-notify-predicate))The built-in
chime-done-keywords-predicateis in the blacklist by default, filtering out DONE items.
All-Day Events
Chime distinguishes between timed events (with specific times like 10:00) and all-day events (without times, such as birthdays or holidays).
What are All-Day Events?
All-day events are org timestamps without a time component:
* Blake's Birthday <2025-12-19 Fri> * Holiday: Christmas <2025-12-25 Thu> * Multi-day Conference <2025-11-10 Mon>--<2025-11-13 Thu>Compare with timed events:
* Meeting That Could've Been an Email <2025-10-28 Tue 14:30-15:30> * Pretend to Floss Consultation SCHEDULED: <2025-10-30 Thu 10:00>Current Behavior
Modeline:
- All-day events are never shown in the modeline
- Only timed events with specific times appear
- Rationale: Modeline shows urgent, time-sensitive items
Notifications:
- All-day events can trigger notifications at configured times
- By default,
chime-day-wide-alert-timesis'("08:00")(morning notification) - When set, chime will notify you of all-day events happening today at those times
Configuring All-Day Event Notifications
To receive notifications for all-day events (like birthdays):
;; Notify at 8:00 AM for all-day events happening today (setq chime-day-wide-alert-times '("08:00")) ;; Multiple notification times (setq chime-day-wide-alert-times '("08:00" "17:00")) ; Morning and evening ;; Disable all-day event notifications (setq chime-day-wide-alert-times nil)Example workflow:
- You have
* Blake's Birthday <2025-12-19 Fri>in your org file - On December 19th at 8:00 AM, chime notifies: "Blake's Birthday is due or scheduled today"
- This gives you a reminder to send birthday wishes or buy a gift
- You have
Showing Overdue TODOs
Control whether overdue TODO items and past events appear alongside all-day event notifications:
;; Show overdue items with all-day event notifications (default: t) (setq chime-show-any-overdue-with-day-wide-alerts t) ;; Only show today's events, not overdue items from past days (setq chime-show-any-overdue-with-day-wide-alerts nil)When enabled (default
t):- Shows today's DEADLINE/SCHEDULED tasks that have passed (e.g., 9am deadline when it's now 2pm)
- Shows today's all-day events even if you launch Emacs after the alert time (e.g., launch at 10am when alert was 8am)
- Shows all-day events from past days (e.g., yesterday's birthday, last week's holiday)
When disabled (
nil):- Shows today's DEADLINE/SCHEDULED tasks that have passed ✓
- Shows today's all-day events even if you launch Emacs late ✓
- Hides all-day events from past days (prevents old birthday/holiday spam) ✓
Most users want the default (
t) to catch overdue items. Disable it if you only want to see today's events and don't want past birthdays/holidays cluttering notifications.Advance Notice for All-Day Events
Get notified about all-day events before they happen — useful for birthdays (buying gifts) or conferences (packing, travel):
;; Only notify on the day of the event (default) (setq chime-day-wide-advance-notice nil) ;; Notify 1 day before as well (setq chime-day-wide-advance-notice 1) ;; → "Blake's birthday is tomorrow" at 08:00 the day before ;; → "Blake's birthday is today" at 08:00 on the day ;; Notify 2 days before (setq chime-day-wide-advance-notice 2)Note: This only affects notifications, not tooltip or modeline display.
Showing All-Day Events in Tooltip
Control whether all-day events (birthdays, holidays, etc.) appear in the modeline tooltip:
;; Show all-day events in tooltip (default: t) (setq chime-tooltip-show-all-day-events t) ;; Hide all-day events from tooltip (setq chime-tooltip-show-all-day-events nil)All-day events are never shown in the modeline itself (only timed events appear there). This setting controls only the tooltip display. Notifications are unaffected.
How alert times and overdue settings interact
Short version:
chime-day-wide-alert-timescontrols when notifications fire.chime-show-any-overdue-with-day-wide-alertscontrols whether you see events from previous days. Today's events always show up regardless — if you launch Emacs at 2 PM and the alert was at 8 AM, you'll still see it.
Common Use Cases
Birthdays:
* Blake Michael's Birthday <2025-02-20 Thu>With
chime-day-wide-alert-timesset to'("08:00"), you'll get a morning reminder on the birthday.Holidays:
* Holiday: New Year's Day <2026-01-01 Thu>Multi-day Events:
* Conference: EmacsCon 2025 <2025-11-10 Mon>--<2025-11-13 Thu>You'll receive notifications on each day of the conference at your configured alert times.
Advanced Settings
Failure Warnings
If the async event check fails repeatedly (e.g., due to a misconfigured org file), chime will show a warning after a configurable number of consecutive failures:
;; Warn after 5 consecutive failures (default) (setq chime-max-consecutive-failures 5) ;; Disable failure warnings (setq chime-max-consecutive-failures 0)Validation Retries
On startup, chime validates that
org-agenda-filesis populated. If your config loads org-agenda-files asynchronously (e.g., via idle timers), chime will retry validation a few times before giving up:;; Retry validation 3 times before showing error (default) (setq chime-validation-max-retries 3) ;; Show error immediately if org-agenda-files is empty (setq chime-validation-max-retries 0)Extra Alert Arguments
Pass additional arguments to every
alertcall (see alert.el documentation for available options):;; Example: always use a specific alert style (setq chime-extra-alert-plist '(:style libnotify))Async Environment Variables
If you have custom variables that need to be available in chime's async subprocess (e.g., for custom predicate functions), add their name patterns:
;; Inject custom variables into the async process (setq chime-additional-environment-regexes '("my-custom-var"))
Full Example Configuration
(use-package chime
:vc (:url "https://github.com/cjennings/chime" :rev :newest)
:after alert
:commands (chime-mode chime-check)
:config
;; Polling interval: check every 60 seconds (default)
(setq chime-check-interval 60)
;; Alert intervals: 5 minutes before (medium) and at event time (high)
(setq chime-alert-intervals '((5 . medium) (0 . high)))
;; Chime sound
(setq chime-play-sound t)
;; Uses bundled chime.wav by default
;; Modeline display - compact format
(setq chime-enable-modeline t)
(setq chime-modeline-lookahead-minutes 180) ; Show events 3 hrs ahead
(setq chime-modeline-format " ⏰%s") ; Minimal prefix
(setq chime-notification-text-format "%t (%u)") ; Title + countdown only
(setq chime-display-time-format-string "%H:%M") ; 24-hour time
(setq chime-time-left-format-short "in %mm") ; Compact: "in 5m"
(setq chime-time-left-format-long "%hh%mm") ; Compact: "1h30m"
(setq chime-time-left-format-at-event "NOW!") ; Custom at-event message
;; Notification settings
(setq chime-notification-title "Reminder")
;; Don't filter by TODO keywords - notify for all events
(setq chime-keyword-whitelist nil)
(setq chime-keyword-blacklist nil)
;; Only notify for non-done items
(setq chime-predicate-blacklist
'(chime-done-keywords-predicate))
;; Enable chime-mode automatically
(chime-mode 1))
Events with SCHEDULED or DEADLINE
* TODO Dentist (Don't Cancel This Time) SCHEDULED: <2025-10-25 Sat 10:00>Repeating Events
Repeating timestamps are fully supported:
* TODO Weekly Meeting (Could've Been an Email) SCHEDULED: <2025-10-25 Sat 14:00 +1w> * TODO Daily Standup (Somehow 45 Minutes) SCHEDULED: <2025-10-25 Sat 09:00 +1d> * TODO Inbox Zero (Aspirational) SCHEDULED: <2025-10-25 Sat 08:00 .+1d>Supported repeaters:
+1w- Repeat weekly from original date.+1d- Repeat daily from completion++1w- Repeat weekly from scheduled date
Known Limitations
S-expression Diary Entries Are Not Supported
Note: org-contacts users will quickly discover the above unsupported format is how org-contacts integrate birthdays into your calendar. If you use org-contacts, you will not be automatically notified about your contacts birthdays.
Specifically, this format is not supported:
* TODO Daily Standup (Somehow 45 Minutes) SCHEDULED: <%%(memq (calendar-day-of-week date) '(1 2 3 4 5))>For those using this format outside of org-contacts, your workaround is to use standard repeating timestamps instead:
* TODO Daily Standup (Somehow 45 Minutes) SCHEDULED: <2025-10-24 Fri 09:00 +1d>For Monday-Friday events, you can either:
- Accept weekend notifications (mark as DONE on weekends)
- Create 5 separate entries, one for each weekday with
+1wrepeater
Integrations
org-gcal
org-gcal syncs Google Calendar events into org files, which is exactly what chime reads. They work well together with no special configuration — just make sure the org files that org-gcal writes to are in your org-agenda-files.
Typical setup:
(use-package org-gcal
:after org
:config
(setq org-gcal-client-id "your-client-id"
org-gcal-client-secret "your-client-secret"
org-gcal-fetch-file-alist
'(("you@gmail.com" . "~/org/gcal.org"))))
;; Make sure chime can see those events
(add-to-list 'org-agenda-files "~/org/gcal.org")
After org-gcal syncs, run M-x chime-refresh-modeline to pick up new events immediately (otherwise chime will find them on the next polling cycle).
One thing to watch: org-gcal uses plain timestamps (<2026-04-05 Sun 14:00>), not SCHEDULED/DEADLINE. Chime handles both, so this works out of the box. If you're filtering by TODO keywords, note that org-gcal events won't have them unless you add keywords to your org-gcal templates.
org-contacts
If you use org-contacts for birthdays, you'll hit a snag: org-contacts uses diary sexps (%%(org-contacts-anniversaries)) that chime's async subprocess can't evaluate, producing "Bad sexp" errors. Chime works around this by converting birthdays to plain org timestamps.
Step 1: Convert existing contacts
;; Load the conversion script
(require 'convert-org-contacts-birthdays
(expand-file-name "convert-org-contacts-birthdays.el"
(file-name-directory (locate-library "chime"))))
;; Convert your contacts file in-place (creates timestamped backup first)
M-x chime-convert-contacts-in-place RET ~/org/contacts.org RET
This adds a yearly repeating timestamp (<1985-03-15 Sat +1y>) below each contact's properties, alongside the existing :BIRTHDAY: property. vCard export still works.
After conversion, comment out the diary sexp in your schedule file:
# %%(org-contacts-anniversaries)Step 2: Set up capture template for new contacts
;; Enable org-contacts integration
(setq chime-org-contacts-file "~/org/contacts.org")
;; Optional: customize capture key (default: "C")
(setq chime-org-contacts-capture-key "C")
This adds an org-capture template (C-c c C) that prompts for contact details and automatically inserts a yearly repeating timestamp if a birthday is provided.
Set chime-org-contacts-file to nil to disable (the default).
Troubleshooting
Enabling Debug Mode
If something isn't working right, enable debug mode for detailed diagnostics in the *Messages* buffer:
;; Enable BEFORE loading chime
(setq chime-debug t)
(require 'chime)
This loads chime-debug.el and gives you these commands:
M-x chime--debug-dump-events– show all stored upcoming eventsM-x chime--debug-dump-tooltip– show tooltip contentM-x chime--debug-config– dump complete configurationM-x chime--debug-show-async-stats– show async process success/failure statsM-x chime-debug-force-check– force an immediate check with diagnostics
It also monitors event loading timing and async process performance in the background, logging to *Messages*.
No Notifications Appearing
Verify a notification daemon is running on your system (e.g.,
dunst,mako, orswayncon Linux; macOS and Windows have built-in notification support)Verify chime-mode is enabled:
M-: chime-modeCheck that alert is configured for your system:
(setq alert-default-style 'libnotify) ; Linux with libnotify ;; or 'notifications for D-Bus, 'osx-notifier for macOS, etc.Manually test:
M-x chime-checkCheck
*Messages*buffer for error messagesEnable debug mode for detailed diagnostics
No Sound Playing
- Verify sound is enabled:
M-: chime-play-soundshould returnt - Check sound file exists:
M-: (file-exists-p chime-sound-file) - Test sound directly:
M-: (play-sound-file chime-sound-file) - Ensure your system has audio support configured
Events Not Being Detected
- Ensure files are in
org-agenda-files - Verify timestamps have time components:
<2025-10-25 Sat 14:00>not<2025-10-25 Sat> - Check filtering settings (keyword/tag whitelist/blacklist)
- Timestamps support both 24-hour (
14:00) and 12-hour (2:00pm,2:00 PM) formats
Multiple Emacs Instances Producing Duplicate Notifications
If you receive duplicate notifications for every event, you likely have multiple Emacs processes running with chime-mode enabled.
Symptoms:
- Receiving 2 (or more) identical notifications for each event
- Notifications appear at the same time but as separate alerts
Common Scenario:
You're running chime with an emacsclient connected to an emacs daemon (emacs --daemon), then launch a separate Emacs process from the command line. Each process runs its own instance of chime-mode, resulting in duplicate notifications.
Example:
# Start emacs daemon with chime-mode enabled
emacs --daemon
# Connect with emacsclient (uses daemon - chime runs here)
emacsclient -c
# Later, accidentally launch standalone Emacs process
emacs & # This creates a SECOND chime instance!Solution:
Check for multiple Emacs processes:
ps aux | grep emacsDecide on your preferred architecture:
- Option A: Use emacs daemon + emacsclient exclusively (recommended for consistency)
- Option B: Use standalone Emacs processes only (simpler, but separate configs)
Kill extra processes:
# To stop the daemon emacsclient -e "(kill-emacs)" # Or kill specific process by PID kill <PID>Verify only one Emacs process is running after cleanup
Prevention:
If using emacs daemon, always connect with
emacsclient -cinstead of launchingemacsAdd shell aliases to prevent accidents:
alias emacs="emacsclient -c -a ''" # Auto-start daemon if not running
Development & Testing
Development
Clone the repo and load from source:
git clone https://github.com/cjennings/chime.git(add-to-list 'load-path "~/path/to/chime")
(require 'chime)
Before submitting changes, check for byte-compilation warnings:
cd tests
make validate # Check parentheses balance
make lint # Full elisp-lint (requires elisp-lint package)Testing
645 ERT tests across 53 files. See TESTING.org for the full story.
Quick start:
cd tests
make test # Run all tests
make test-file FILE=modeline # Run specific test fileHistory
CHIME started life as org-wild-notifier, written by Artem Khramov. I relied on it daily for years, so when Artem deprecated it on Aug 2, 2025 in favor of org-alert, I adopted it rather than let it rot. One bug fix led to another, features crept in, and eventually it had changed enough to deserve its own name.
All credit for the original idea and architecture goes to Artem. I'm just the person who couldn't let it go.
Bug reports, feature requests, and PRs are welcome — consider this repo CHIME's permanent home.
Migration from org-wild-notifier
If you're migrating from org-wild-notifier, update your configuration:
- Change package name:
(require 'org-wild-notifier)→(require 'chime)
- Update all configured variable names:
org-wild-notifier-*→chime-*
- Update configured function names:
org-wild-notifier-mode→chime-modeorg-wild-notifier-check→chime-check
- Note: The
:WILD_NOTIFIER_NOTIFY_BEFORE:/:CHIME_NOTIFY_BEFORE:property has been removed. Use the globalchime-alert-intervalsvariable instead (e.g.,(setq chime-alert-intervals '((30 . low) (15 . medium) (5 . medium) (0 . high)))).
