💌 A beautiful, fast and fully open source mail client for Mac, Windows and Linux.
Find a file
Ben Gotow 6760c0fd1e
Implement Calendar event syncback for changes (#2574)
* Add implementation plan for calendar ICS helpers and recurring event dialog

This plan addresses two issues with calendar event drag persistence:

1. ICS data inconsistency - currently only cached fields are updated,
   not the underlying ICS data. Plan includes a new ics-event-helpers.ts
   module with functions to create, update, and modify ICS data.

2. Missing recurring event handling - no dialog asking whether to modify
   this occurrence or all occurrences. Plan includes a confirmation
   dialog component and integration with the drag persistence flow.

* Implement ICS helpers and recurring event dialog for calendar events

This change ensures calendar event modifications properly update the
underlying ICS data, not just the cached recurrenceStart/recurrenceEnd fields.

New files:
- ics-event-helpers.ts: Centralized helpers for ICS manipulation
  - createICSString: Create new ICS events from scratch
  - updateEventTimes: Update DTSTART/DTEND in existing ICS
  - createRecurrenceException: Create EXDATE + exception event
  - updateRecurringEventTimes: Shift entire recurring series
  - isRecurringEvent/getRecurrenceInfo: Query recurrence status

- recurring-event-dialog.ts: Dialog asking "this occurrence only"
  vs "all occurrences" when modifying recurring events

Modified files:
- mailspring-calendar.tsx: Updated _persistDragChange to use ICS helpers
  and show recurring event dialog. Added separate methods for simple
  event changes, single occurrence exceptions, and all-occurrences updates.
  Undo/redo now properly restores ICS data.

- calendar-event-popover.tsx: Updated saveEdits to use ICS helpers
  and handle recurring events with the same dialog flow.

- mailspring-exports.js/d.ts: Export ICSEventHelpers module

Fixes the issue where dragging events appeared to move them but the
underlying ICS data was not updated, causing sync inconsistencies.

* Add remaining work plan for calendar ICS implementation

Documents the prioritized list of remaining issues identified during
code review, including:
- High: recurring event deletion, quick event ICS generation, undo fix
- Medium: deprecated lifecycle, race conditions, timezone handling
- Low: validation, null checks, localization

Includes implementation details and recommended order of work.

* Add ICS generation, recurring deletion dialog, and fix deprecated lifecycle

Three improvements to calendar event handling:

1. Quick Event Creation (quick-event-popover.tsx):
   - Now generates proper ICS data using ICSEventHelpers.createICSString()
   - Sets ics and icsuid fields on new Event models
   - Exported generateUID() from ics-event-helpers.ts

2. Recurring Event Deletion (mailspring-calendar.tsx):
   - Added recurring event dialog to _onDeleteSelectedEvents
   - "This occurrence only" adds EXDATE to master event
   - "All occurrences" deletes the entire series
   - Added addExclusionDate() helper to ics-event-helpers.ts
   - Split deletion logic into _deleteEvent, _deleteOccurrence, _deleteEntireEvent

3. React Lifecycle Fix (mailspring-calendar.tsx):
   - Changed deprecated componentWillMount to componentDidMount
   - Improves React 18 compatibility

* Improve timezone handling and add validation in ICS helpers

Timezone improvements:
- createICALTime now properly preserves timezone from original events
- Uses convertToZone for correct timezone conversion instead of just
  attaching zone after creation
- formatDateOnly now uses local date (not UTC) for all-day events to
  prevent off-by-one day errors
- createICSString now accepts optional timezone parameter
- createRecurrenceException preserves master event's timezone

Validation improvements:
- Added validateTimestamps helper to check for valid positive numbers
- Validates that end time is not before start time
- Added null checks for VEVENT component in all functions that need it
- Functions now throw descriptive errors for malformed ICS

Also renamed formatDateTime to formatDateTimeUTC for clarity.

* Replace any types with proper TypeScript interfaces and extract recurring event utility

- Add UndoBlock interface to UndoRedoStore with public queueUndoBlock method
- Export UndoBlock from mailspring-exports for external use
- Replace `any[]` attendees type with EventAttendee interface
- Fix HTMLMetaElement cast in extractNotesFromDescription
- Add proper types to EventAttendeesInput props and methods
- Create recurring-event-actions.ts shared utility with:
  - modifySimpleEvent: Update non-recurring events
  - createOccurrenceException: Create exception for single occurrence
  - modifyAllOccurrences: Shift entire recurring series
  - modifyEventWithRecurringSupport: Main entry point with dialog handling
- Refactor mailspring-calendar.tsx to use shared utility (removes ~90 lines)
- Refactor calendar-event-popover.tsx to use shared utility (removes ~70 lines)
- Remove duplicate recurring event handling code from both components

* Use task-based undo for calendar events instead of custom UndoBlock

Following the established pattern from SyncbackMetadataTask, this change
enhances SyncbackEventTask to support undo by storing original event data:

- Add undoData and taskDescription attributes to SyncbackEventTask
- Add canBeUndone getter that returns true when undoData is provided
- Implement createUndoTask() that swaps current/original state (like redo)
- Update forUpdating() to accept undoData and description parameters

Update recurring-event-actions.ts to use task-based undo:
- Capture event snapshot before modifications
- Pass undoData to SyncbackEventTask.forUpdating() calls
- Pass description for undo toast display

Remove custom UndoBlock from UndoRedoStore:
- Remove exported UndoBlock interface
- Remove queueUndoBlock method
- Keep internal UndoBlock type for task-based undo

Update mailspring-calendar.tsx:
- Remove manual _registerUndoAction method
- Update keyboard handler to pass undoData to task
- Update drag handler to pass description to shared utility
- Undo is now automatically handled by UndoRedoStore._onQueue

This aligns with how other undoable tasks work (ChangeStarredTask,
SyncbackMetadataTask) and removes the need for custom undo handling.

* Add undo-redo task pattern documentation for future implementers

- Create docs/undo-redo-task-pattern.md with comprehensive guide
- Document toggle pattern (ChangeStarredTask) and snapshot pattern
- Include implementation steps, anti-patterns, and related files
- Update CLAUDE.md to reference the new documentation

* Fix recurring event handling and undo/redo issues in calendar

- Keyboard events (arrow keys) now show recurring event dialog like drag does
- Single occurrence deletion now supports undo via undoData
- Add newData snapshot to SyncbackEventTask for reliable redo
- Override createIdenticalTask to use captured snapshots

Fixes three PR review findings:
1. Arrow key changes bypassed recurring event dialog
2. _deleteOccurrence didn't pass undoData (not undoable)
3. Redo now uses captured snapshots instead of potentially stale event refs

* Improve TypeScript typing and add user error feedback

- Add type annotation to constructor props parameter
- Show error dialogs when calendar operations fail (delete, keyboard move, drag)
- Add documentation about Event.clone() deep clone requirement for undo/redo

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-20 10:28:02 -06:00
.circleci Add SECURITY.md 2022-10-25 06:13:17 -05:00
.claude/skills Add plugin system architecture documentation and Claude skill (#2538) 2026-01-04 21:00:53 -06:00
.github Skip provisioning profile for now, will revisit in next release 2026-01-12 17:12:43 -06:00
.snapcraft Fix snap credentials encrypted in Travis 2024-09-13 23:21:25 -05:00
.vscode Upgrade to Electron 8, improve TS usage and TS errors outside calendar [requires re- npm install] (#2284) 2021-02-14 15:58:22 -06:00
app Implement Calendar event syncback for changes (#2574) 2026-01-20 10:28:02 -06:00
docs Implement Calendar event syncback for changes (#2574) 2026-01-20 10:28:02 -06:00
mailsync@80a361685a 1.17.1 2026-01-15 14:04:48 -06:00
plans Implement event search for calendar feature (#2533) 2026-01-05 22:46:44 -06:00
screenshots Add screenshot back to README 2017-08-02 12:46:04 -07:00
scripts Add an env var for running Intel builds on MacOS Silicon 2025-09-14 22:09:13 -05:00
snap Move Snap-aware SSL certificate loading into Mailsync code 2026-01-12 14:39:47 -06:00
.eslintrc Fix or downgrade remaining eslint warnings to make Travis CI builds succeed 2021-01-19 10:28:44 -06:00
.gitignore snap: Ignore snap/.snapcraft files 2021-05-03 08:07:00 -05:00
.gitmodules ci: Use public URI for the Mailsync repository 2021-05-03 08:07:00 -05:00
.prettierrc Adopt prettier , upgrade ESLint 2017-09-26 11:33:08 -07:00
book.json Switch product name to “Mailspring” 2017-09-05 13:40:25 -07:00
CHANGELOG.md 1.17.1 2026-01-15 14:04:48 -06:00
CLAUDE.md Implement Calendar event syncback for changes (#2574) 2026-01-20 10:28:02 -06:00
CODE_OF_CONDUCT.md Add CoC, Discourse links 2021-01-12 18:00:18 -08:00
CONTRIBUTING.md Set node version to 16 2022-02-27 21:49:17 -06:00
LICENSE.md fix(license): Swap ref to GPLv3 with the whole thing so GitHub picks it up 2016-10-15 10:48:44 -07:00
LOCALIZATION.md Improve localization (#2466) 2023-10-16 08:55:45 -05:00
package-lock.json Upgrade TypeScript from version 3 to 5 (#2547) 2026-01-06 12:41:35 -06:00
package.json Remove unused Grunt tasks and clean up eslint task (#2569) 2026-01-19 22:12:33 -06:00
PLUGIN_SYSTEM_ARCHITECTURE.md Add plugin system architecture documentation and Claude skill (#2538) 2026-01-04 21:00:53 -06:00
README.md Remove Travis CI and AppVeyor configuration (#2555) 2026-01-08 20:17:03 -06:00
SECURITY.md Add SECURITY.md 2022-10-25 06:13:17 -05:00

💌 Mailspring

Mailspring is a new version of Nylas Mail maintained by one of the original authors. It's faster, leaner, and shipping today! It replaces the JavaScript sync code in Nylas Mail with a new C++ sync engine based on Mailcore2. It uses roughly half the RAM and CPU of Nylas Mail and idles with almost zero "CPU Wakes", which translates to great battery life. It also has an entirely revamped composer and other great new features.

Mailspring's UI is open source (GPLv3) and written in TypeScript with Electron and React - it's built on a plugin architecture and was designed to be easy to extend. Check out CONTRIBUTING.md to get started!

Mailspring's sync engine is spawned by the Electron application and runs locally on your computer. It is open source (GPLv3) and written in C++ and C. For convenience, however, when you set up your development environment, Mailspring uses the latest version of the sync engine we've shipped for your platform so you don't need to pull sources or install its compile-time dependencies.

Mailspring Screenshot

Features

Mailspring comes packed with powerful features like Unified Inbox, Snooze, Send Later, Mail Rules, Templates and more. Mailspring Pro, which you can unlock with a monthly subscription, adds even more features for people who send a ton of email: link tracking, read receipts, mailbox analytics, contact and company profiles. All of these features run in the client - Mailspring does not send your email credentials to the cloud. For a full list of features, check out getmailspring.com.

Download Mailspring

You can download compiled versions of Mailspring for Windows, Mac OS X, and Linux (deb, rpm and snap) from https://getmailspring.com/download.

Getting Help

You can find community-based help and discussion with other Mailspring users on our Discourse community.

Contributing

Mailspring is entirely open-source. Pull requests and contributions are welcome! There are three ways to contribute: building a plugin, building a theme, and submitting pull requests to the project itself. When you're getting started, you may want to join our Discourse so you can ask questions and learn from other people doing development.

Contributor Covenant

Running Mailspring from Source

To install all dependencies and run Mailspring from its source code, run the following commands from the root directory of the Mailspring repository:

export npm_config_arch=x64 # If you are on an M1 / Apple Silicon Mac
npm install
npm start

You can attach command line parameters by separating them using a double hyphen:

npm start -- --help

Building Mailspring

To build Mailspring, you need to run the following command from the root directory of the Mailspring repository:

npm run-script build

Building A Plugin

Plugins lie at the heart of Mailspring and give it its powerful features. Building your own plugins allows you to integrate the app with other tools, experiment with new workflows, and more. Follow the Getting Started guide to write your first plugin in five minutes.

A plugin "store" like the Chrome Web Store is coming soon, and will make it easy for other users to discover plugins you create. (Right now, users need to "sideload" the plugins into the app by downloading them and copying them into place.)

You can share and browse Mailspring Plugins, and discuss plugin development with other developers, on our Discourse.

Building a Theme

The Mailspring user interface is styled using CSS, which means it's easy to modify and extend. Mailspring comes stock with a few beautiful themes, and there are many more which have been built by community developers. To start creating a theme, clone the theme starter!

If you are updating an existing Nylas theme for Mailspring here is a step by step tutorial. Notice: as part of the update process you will probably need to import mailspring base variables.

You can share and browse Mailspring Themes, and discuss theme development with other developers, on our Discourse.

Localizing / Translating

Mailspring (1.5.0 and above) supports localization. If you're a fluent speaker of another language, we'd love your help improving translations. Check out the LOCALIZATION guide for more information. You can discuss localization and translation with other developers on our Discourse.

Contributing to Mailspring Core

Pull requests are always welcome - check out CONTRIBUTING for more information about setting up the development environment, running tests locally, and submitting pull requests.