iCalendar export: RRULE contains SKIP without RSCALE (invalid per RFC 7529) when JSCalendar skip/rscale are set

Issue Description

When a JMAP client sets the JSCalendar recurrenceRule properties
rscale: “gregorian” and skip: “omit” (these are the RFC 8984 DEFAULT values;
real-world example: the Bulwark webmail client always sends them), Stalwart’s
iCalendar export renders the rule as e.g.

RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=2TU;WKST=MO;SKIP=OMIT

while the rscale value is exported separately as
JSPROP;JSPTR=recurrenceRule/rscale:“gregorian”

RFC 7529 §4.1 requires: the SKIP rule part MUST NOT be specified unless the
RSCALE rule part is specified. The emitted RRULE is therefore invalid, and
strict parsers reject it (e.g. sabre/vobject: “Not supported: SKIP”), which
breaks recurrence expansion in downstream CalDAV clients.

Creating the same event WITHOUT rscale/skip produces a clean RRULE
(RRULE:FREQ=WEEKLY), so the problem is only triggered when the client sends
these (perfectly legal) JSCalendar defaults.

Expected Behavior

Either omit SKIP=OMIT/RSCALE=GREGORIAN entirely on export (they are the
defaults, semantics are identical), or emit RSCALE=GREGORIAN together with
SKIP in the RRULE so the rule stays valid per RFC 7529.

Actual Behavior

RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=2TU;WKST=MO;SKIP=OMIT (no RSCALE)
JSPROP;JSPTR=recurrenceRule/rscale:“gregorian”

Reproduction Steps

  1. Create a recurring event via JMAP with the RFC 8984 default values set explicitly:

curl -u user:pass https://mail.example.com/jmap/
-H ‘Content-Type: application/json’
-d ‘{“using”:[“urn:ietf:params:jmap:core”,“urn:ietf:params:jmap:calendars”],
“methodCalls”:[[“CalendarEvent/set”,{“accountId”:“”,
“create”:{“t1”:{“calendarIds”:{“”:true},
“title”:“Recurring test”,“start”:“2026-07-21T19:00:00”,
“timeZone”:“Europe/Berlin”,“duration”:“PT30M”,
“recurrenceRule”:{“@type”:“RecurrenceRule”,“frequency”:“monthly”,
“interval”:1,“rscale”:“gregorian”,“skip”:“omit”,
“firstDayOfWeek”:“mo”,“byDay”:[{“day”:“tu”,“nthOfPeriod”:2}]}}}},“c1”]]}’

  1. GET the created resource via CalDAV.
  2. Observe SKIP=OMIT in the RRULE without an RSCALE part.

Relevant Log Output

No relevant errors at default logging level.

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

This isn’t covered in the current docs. A community member or staff member will follow up.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

This will be fixed in v0.16.14.