671145939b Answer or counter an invitation from the calendar's right-click menu (#2928)
* Advance SEQUENCE once per revision, from the caller that assembles it

RFC 5546 section 2.1.4: the organizer increments SEQUENCE when they change
something that matters to the guests, and a guest's client ignores an update
whose SEQUENCE has not advanced. Five helpers each bumped it when the property
happened to be present, so one save that changed time, guests and recurrence
advanced three times, and a save on an event with no SEQUENCE never advanced
at all. The helpers now leave SEQUENCE alone; the caller that assembles a
revision calls bumpEventSequence once, on the master or on the edited
occurrence's exception, and an absent SEQUENCE counts as zero (RFC 5545
section 3.7.4).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Record an RSVP on our own copy of the event, and add the invitation when there is none

Answering an invitation from the message did one of the two things an RSVP
is: it emailed the organizer a REPLY. Under CalDAV the attendee also writes
their PARTSTAT back to their own copy of the event (RFC 6638 section 3.2.5),
and for a Google guest that write is what registers: against a real Google
organizer the emailed REPLY was delivered and ignored, while the PARTSTAT
written over CalDAV showed on the organizer's calendar within a minute. So
the calendar kept showing the meeting as unanswered - or, for an invitation
Google had not put on the calendar at all, showed nothing.

resolveRSVPTarget picks the copy we are entitled to write to: one UID can sit
on our calendar, a room's and a colleague's at once, and the header now says
which calendar the answer goes to, or why it will only be emailed. When the
event is on none of our calendars, accepting creates it on our own (never for
an invitation naming us as ORGANIZER, which a scheduling server would turn
into outbound mail). The copy's VEVENT shown and answered is the one
eventForInvitation picks for the invitation, and the REPLY is addressed to
the organizer the mailed invitation names, because Google rewrites ORGANIZER
on a shared calendar's copy to an address nobody reads.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Show what an invitation conflicts with on the calendar

Answering from the message meant opening the calendar to check the slot.
ICSEventHelpers.findConflicts expands the account's stored events across the
slot being answered - the next occurrence of a recurring invitation, else the
event itself - honouring TRANSP:TRANSPARENT, CANCELLED and this account's own
DECLINED, leaving out all-day events, read-only subscribed feeds, calendars
hidden in the sidebar and calendars the server says are someone else's. The
header lists what overlaps, the way Google Calendar does.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Propose a new time for an invitation

Only the organizer may revise a meeting (RFC 5546 section 2.1.4), so an
attendee who cannot make the slot could accept, decline, or reply by hand.
iTIP's COUNTER (section 3.2.7) is the third answer - Google Calendar's
"Propose a new time" - and the sync engine has accepted it since
Mailspring-Sync#130. This adds the pieces the calendar's context menu (#2928)
wires up: ProposeTimePopover picks the slot (dates only for an all-day
invitation) and an optional note, createCounterProposal builds the COUNTER
from the invitation with only the proposer listed and one occurrence named
by RECURRENCE-ID, and EventRSVPTask sends it without marking the invitation
answered.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Accept a proposed time from a guest of a meeting we organize

A COUNTER that arrives by email asks us, as organizer, to move the meeting.
The header shows the proposed slot and offers to move our copy to it - but
only when our own synced copy says this account organizes the meeting (RFC
5546 section 2.1.4) and the message's sender is on its guest list (section
3.2.7). Nothing in the attachment is trusted for that: its UID, ORGANIZER and
ATTENDEE list are the sender's, and a UID is not a secret. When either check
fails the proposal still renders, with a line saying why it cannot be applied.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Answer or counter an invitation from the calendar's right-click menu

An invitation that this client's own accept flow put on the calendar has no
email to hand by the time someone wants to change their answer, and an
attendee may not edit the meeting itself (RFC 5546 section 2.1.4). The event's
context menu now carries Accept / Maybe / Decline - which both email the
organizer a REPLY and write our PARTSTAT onto our copy, refusing a copy the
server says is somebody else's - and Propose New Time, which sends a COUNTER
naming the occurrence that was right-clicked (dates only for an all-day one)
and writes nothing locally, so it is offered on a read-only calendar too.
Occurrences learn isMine (this account organizes the event, or nobody does)
so the menu can say Edit or View, and the organizer address is normalised
once in the data source.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Advance SEQUENCE once per revision, from the caller that assembles it

RFC 5546 section 2.1.4: the organizer increments SEQUENCE when they change
something that matters to the guests, and a guest's client ignores an update
whose SEQUENCE has not advanced. Five helpers each bumped it when the property
happened to be present, so one save that changed time, guests and recurrence
advanced three times, and a save on an event with no SEQUENCE never advanced
at all. The helpers now leave SEQUENCE alone; the caller that assembles a
revision calls bumpEventSequence once, on the master or on the edited
occurrence's exception, and an absent SEQUENCE counts as zero (RFC 5545
section 3.7.4).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Drop the SEQUENCE spec blocks now carried by master

#2923 landed as 8f670263e; this branch's own copies of its describe blocks sat at a different
position than master's and survived the merge as duplicates.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Answer an invitation to one occurrence on that occurrence of our copy

planRSVPWrite ran updateAttendeeStatus over the synced copy, which sets PARTSTAT on every
VEVENT, so accepting the 15 Sep email of a weekly series wrote ACCEPTED on the master and on
the other moved week too. A Google organizer takes the answer from this CalDAV write and
ignores the emailed REPLY (manilabui measured both on #2924), so one occurrence's answer
became the whole series' there.

updateOccurrenceAttendeeStatus changes only the VEVENT the invitation's RECURRENCE-ID names,
and stores the emailed VEVENT as that occurrence's exception when the copy has none for it,
bringing its VTIMEZONE along. The planner takes that path whenever the invitation names an
occurrence. The header spec's occurrence cases now run with a writable calendar of ours, so
they reach the write.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Show conflicts only on a request, and name the day when it is not the one shown

A REPLY is about a slot we picked ourselves, so what overlaps it is not news; conflicts now
render only for a REQUEST. For an invitation to a whole series the header dates it by its
first occurrence while the slot checked is the next one, so a clash on another day than the
one shown read as a clash on the shown date; each such line now starts with the day it is on.
Both found by manilabui on #2925.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Name a countered occurrence the way its series writes DTSTART

createCounterProposal copied the moved occurrence's VEVENT and then wrote the UTC instant onto
its RECURRENCE-ID with updatePropertyWithValue, which keeps the property's TZID parameter:
RECURRENCE-ID;TZID=America/Los_Angeles:20250716T153000Z, which RFC 5545 section 3.2.19
forbids and a server honouring the TZID reads hours off. Every RECURRENCE-ID Google writes
carries a TZID, so this was every moved occurrence there (found by manilabui on #2926).

A moved occurrence now keeps the RECURRENCE-ID the organizer gave it, untouched. An occurrence
cut from the master is named in the form of the series' DTSTART (RFC 5545 section 3.8.4.4):
a DATE for an all-day series, local time with the same TZID for a zoned one, UTC otherwise or
when the zone cannot be identified.

Two mutants she found alive now die: a counter drops RDATE as well as RRULE, and the all-day
To picker offers the last day rather than the exclusive end.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Withhold a counter for a series that names no occurrence

Applying such a COUNTER ran updateEventTimes on the master, re-basing every occurrence on
the proposed date: on a weekly series from 5 January a proposal for 8 October took the 43
occurrences through October down to 4 (manilabui, #2927). #2926 always names the occurrence,
but another client need not. counterProposalProblem now reports 'series-without-occurrence'
when our copy recurs and the proposal carries no RECURRENCE-ID, and the header explains
instead of offering the move; Thunderbird's invitation bar draws the same line.

Two terms were unguarded: the occurrence branch's SEQUENCE bump and the all-day flag on both
branches now each have a spec.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Answer one occurrence from the calendar, and read ownership against the event's account

Answering from the menu on one occurrence of a series set PARTSTAT on every VEVENT and
replied with the whole series, RRULE and all (manilabui, #2928). On a series the menu now
asks, as Google Calendar and this calendar's Delete do, whether the answer is for this
occurrence or all of them. One occurrence is answered on its own VEVENT - the existing
exception, or the occurrence cut from the master by the new occurrenceIcs - and in a REPLY
that names it by RECURRENCE-ID (RFC 5546 section 3.2.3).

isMine used Contact.isMe(), which matches any connected account, while the RSVP helpers
scope me to the event's account; an invitation from another of our accounts came out as
ours, editable and draggable. Ownership and pending now use the event's account, like
myAttendeeEmail. The menu decides editability itself from readOnly and isMine, so the gate
cannot drift from the occurrence it is shown for.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Brian Hellman <brian.hellman@linbit.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 09:42:00 -05:00
2026-10-01 16:17:08 -05:00
2017-08-02 12:46:04 -07:00
2026-07-20 14:11:43 -05:00
2026-05-05 22:23:02 -05:00
2026-10-01 16:51:12 -05:00
2021-01-12 18:00:18 -08:00
2023-10-16 08:55:45 -05:00
2026-10-02 09:49:08 -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.

S
Description
💌 A beautiful, fast and fully open source mail client for Mac, Windows and Linux.
Readme GPL-3.0
191 MiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 40%
JavaScript 34.6%
Fluent 12.1%
HTML 8.8%
Less 3.2%
Other 1.2%