[evolution-ews/wip/mcrha/office365: 49/50] Prepare for tasks (to-do), but do not use the code, because it's in Beta only




commit edfffcc92b4a4832c9a48131260b78a00ecb9e98
Author: Milan Crha <mcrha redhat com>
Date:   Fri Jul 31 14:02:36 2020 +0200

    Prepare for tasks (to-do), but do not use the code, because it's in Beta only
    
    ...and some parts do not work.

 src/Microsoft365/calendar/e-cal-backend-m365.c   |  944 ++++++++++++++---
 src/Microsoft365/common/e-m365-connection.c      | 1181 +++++++++++++++++++++-
 src/Microsoft365/common/e-m365-connection.h      |  188 +++-
 src/Microsoft365/common/e-m365-json-utils.c      |  332 ++++++
 src/Microsoft365/common/e-m365-json-utils.h      |   87 ++
 src/Microsoft365/common/e-source-m365-folder.c   |    4 +-
 src/Microsoft365/registry/e-m365-backend.c       |   69 ++
 src/Microsoft365/registry/e-source-m365-deltas.c |    2 +-
 8 files changed, 2634 insertions(+), 173 deletions(-)
---
diff --git a/src/Microsoft365/calendar/e-cal-backend-m365.c b/src/Microsoft365/calendar/e-cal-backend-m365.c
index 7d63fee9..7fcfbacd 100644
--- a/src/Microsoft365/calendar/e-cal-backend-m365.c
+++ b/src/Microsoft365/calendar/e-cal-backend-m365.c
@@ -120,27 +120,62 @@ ecb_m365_split_extra (gchar *inout_extra,
 
 static void
 ecb_m365_get_uid (ECalBackendM365 *cbm365,
-                 EM365Event *m365_event,
+                 JsonObject *m365_object,
                  ICalComponent *inout_comp,
                  ICalPropertyKind prop_kind)
 {
-       i_cal_component_set_uid (inout_comp, e_m365_event_get_id (m365_event));
+       const gchar *id;
+
+       switch (i_cal_component_isa (inout_comp)) {
+       case I_CAL_VEVENT_COMPONENT:
+               id = e_m365_event_get_id (m365_object);
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               id = e_m365_task_get_id (m365_object);
+               break;
+       default:
+               g_warn_if_reached ();
+               return;
+       }
+
+       i_cal_component_set_uid (inout_comp, id);
 }
 
 static void
 ecb_m365_get_date_time (ECalBackendM365 *cbm365,
-                       EM365Event *m365_event,
+                       JsonObject *m365_object,
                        ICalComponent *inout_comp,
                        ICalPropertyKind prop_kind)
 {
        time_t tt = (time_t) 0;
 
-       if (prop_kind == I_CAL_CREATED_PROPERTY)
-               tt = e_m365_event_get_created_date_time (m365_event);
-       else if (prop_kind == I_CAL_LASTMODIFIED_PROPERTY)
-               tt = e_m365_event_get_last_modified_date_time (m365_event);
-       else
+       if (prop_kind == I_CAL_CREATED_PROPERTY) {
+               switch (i_cal_component_isa (inout_comp)) {
+               case I_CAL_VEVENT_COMPONENT:
+                       tt = e_m365_event_get_created_date_time (m365_object);
+                       break;
+               case I_CAL_VTODO_COMPONENT:
+                       tt = e_m365_task_get_created_date_time (m365_object);
+                       break;
+               default:
+                       g_warn_if_reached ();
+                       return;
+               }
+       } else if (prop_kind == I_CAL_LASTMODIFIED_PROPERTY) {
+               switch (i_cal_component_isa (inout_comp)) {
+               case I_CAL_VEVENT_COMPONENT:
+                       tt = e_m365_event_get_last_modified_date_time (m365_object);
+                       break;
+               case I_CAL_VTODO_COMPONENT:
+                       tt = e_m365_task_get_last_modified_date_time (m365_object);
+                       break;
+               default:
+                       g_warn_if_reached ();
+                       return;
+               }
+       } else {
                g_warn_if_reached ();
+       }
 
        if (tt > (time_t) 0) {
                ICalProperty *prop;
@@ -183,22 +218,45 @@ ecb_m365_get_timezone_sync (ECalBackendM365 *cbm365,
 
 static void
 ecb_m365_get_date_time_zone (ECalBackendM365 *cbm365,
-                            EM365Event *m365_event,
+                            JsonObject *m365_object,
                             ICalComponent *inout_comp,
                             ICalPropertyKind prop_kind)
 {
-       EM365DateTimeWithZone *value = NULL;
+       EM365DateTimeWithZone *value;
        ICalTimezone *tz;
        ICalTime *itt;
        time_t tt;
        const gchar *tzid, *zone;
+       gboolean is_date;
 
        if (prop_kind == I_CAL_DTSTART_PROPERTY) {
-               value = e_m365_event_get_start (m365_event);
-               tzid = e_m365_event_get_original_start_timezone (m365_event);
+               switch (i_cal_component_isa (inout_comp)) {
+               case I_CAL_VEVENT_COMPONENT:
+                       value = e_m365_event_get_start (m365_object);
+                       tzid = e_m365_event_get_original_start_timezone (m365_object);
+                       is_date = e_m365_event_get_is_all_day (m365_object);
+                       break;
+               case I_CAL_VTODO_COMPONENT:
+                       value = e_m365_task_get_start_date_time (m365_object);
+                       tzid = "UTC";
+                       is_date = TRUE;
+                       break;
+               default:
+                       g_warn_if_reached ();
+                       return;
+               }
        } else if (prop_kind == I_CAL_DTEND_PROPERTY) {
-               value = e_m365_event_get_end (m365_event);
-               tzid = e_m365_event_get_original_end_timezone (m365_event);
+               value = e_m365_event_get_end (m365_object);
+               tzid = e_m365_event_get_original_end_timezone (m365_object);
+               is_date = e_m365_event_get_is_all_day (m365_object);
+       } else if (prop_kind == I_CAL_COMPLETED_PROPERTY) {
+               value = e_m365_task_get_completed_date_time (m365_object);
+               tzid = "UTC";
+               is_date = TRUE;
+       } else if (prop_kind == I_CAL_DUE_PROPERTY) {
+               value = e_m365_task_get_due_date_time (m365_object);
+               tzid = "UTC";
+               is_date = TRUE;
        } else {
                g_warn_if_reached ();
                return;
@@ -218,7 +276,7 @@ ecb_m365_get_date_time_zone (ECalBackendM365 *cbm365,
        if (!tz)
                tz = i_cal_timezone_get_utc_timezone ();
 
-       itt = i_cal_time_new_from_timet_with_zone (tt, e_m365_event_get_is_all_day (m365_event), tz);
+       itt = i_cal_time_new_from_timet_with_zone (tt, is_date, tz);
 
        tzid = e_m365_tz_utils_get_ical_equivalent (tzid);
 
@@ -227,13 +285,17 @@ ecb_m365_get_date_time_zone (ECalBackendM365 *cbm365,
 
        tz = ecb_m365_get_timezone_sync (cbm365, tzid);
 
-       if (tz && !e_m365_event_get_is_all_day (m365_event))
+       if (tz && !is_date)
                i_cal_time_convert_to_zone_inplace (itt, tz);
 
        if (prop_kind == I_CAL_DTSTART_PROPERTY)
                i_cal_component_set_dtstart (inout_comp, itt);
-       else /* I_CAL_DTEND_PROPERTY */
+       else if (prop_kind == I_CAL_DTEND_PROPERTY)
                i_cal_component_set_dtend (inout_comp, itt);
+       else if (prop_kind == I_CAL_COMPLETED_PROPERTY)
+               i_cal_component_take_property (inout_comp, i_cal_property_new_completed (itt));
+       else /* if (prop_kind == I_CAL_DUE_PROPERTY) */
+               i_cal_component_set_due (inout_comp, itt);
 
        g_clear_object (&itt);
 }
@@ -255,11 +317,37 @@ ecb_m365_add_date_time_zone (ECalBackendM365 *cbm365,
        if (prop_kind == I_CAL_DTSTART_PROPERTY) {
                new_value = i_cal_component_get_dtstart (new_comp);
                old_value = old_comp ? i_cal_component_get_dtstart (old_comp) : NULL;
-               add_func = e_m365_event_add_start;
+
+               switch (i_cal_component_isa (new_comp)) {
+               case I_CAL_VEVENT_COMPONENT:
+                       add_func = e_m365_event_add_start;
+                       break;
+               case I_CAL_VTODO_COMPONENT:
+                       add_func = e_m365_task_add_start_date_time;
+                       break;
+               default:
+                       g_warn_if_reached ();
+                       return;
+               }
        } else if (prop_kind == I_CAL_DTEND_PROPERTY) {
                new_value = i_cal_component_get_dtend (new_comp);
                old_value = old_comp ? i_cal_component_get_dtend (old_comp) : NULL;
                add_func = e_m365_event_add_end;
+       } else if (prop_kind == I_CAL_COMPLETED_PROPERTY) {
+               ICalProperty *new_prop, *old_prop;
+
+               new_prop = i_cal_component_get_first_property (new_comp, prop_kind);
+               old_prop = old_comp ? i_cal_component_get_first_property (old_comp, prop_kind) : NULL;
+               new_value = new_prop ? i_cal_property_get_completed (new_prop) : NULL;
+               old_value = old_prop ? i_cal_property_get_completed (old_prop) : NULL;
+               add_func = e_m365_task_add_completed_date_time;
+
+               g_clear_object (&new_prop);
+               g_clear_object (&old_prop);
+       } else if (prop_kind == I_CAL_DUE_PROPERTY) {
+               new_value = i_cal_component_get_due (new_comp);
+               old_value = old_comp ? i_cal_component_get_due (old_comp) : NULL;
+               add_func = e_m365_task_add_due_date_time;
        } else {
                g_warn_if_reached ();
                return;
@@ -318,13 +406,23 @@ ecb_m365_add_date_time_zone (ECalBackendM365 *cbm365,
 
 static void
 ecb_m365_get_categories (ECalBackendM365 *cbm365,
-                        EM365Event *m365_event,
+                        JsonObject *m365_object,
                         ICalComponent *inout_comp,
                         ICalPropertyKind prop_kind)
 {
        JsonArray *categories;
 
-       categories = e_m365_event_get_categories (m365_event);
+       switch (i_cal_component_isa (inout_comp)) {
+       case I_CAL_VEVENT_COMPONENT:
+               categories = e_m365_event_get_categories (m365_object);
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               categories = e_m365_task_get_categories (m365_object);
+               break;
+       default:
+               g_warn_if_reached ();
+               return;
+       }
 
        if (categories) {
                GString *categories_str = NULL;
@@ -431,6 +529,25 @@ ecb_m365_add_categories (ECalBackendM365 *cbm365,
 {
        GHashTable *old_value = NULL;
        GSList *new_value = NULL;
+       void (* begin_categories_func) (JsonBuilder *builder);
+       void (* end_categories_func) (JsonBuilder *builder);
+       void (* add_category_func) (JsonBuilder *builder, const gchar *category);
+
+       switch (i_cal_component_isa (new_comp)) {
+       case I_CAL_VEVENT_COMPONENT:
+               begin_categories_func = e_m365_event_begin_categories;
+               end_categories_func = e_m365_event_end_categories;
+               add_category_func = e_m365_event_add_category;
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               begin_categories_func = e_m365_task_begin_categories;
+               end_categories_func = e_m365_task_end_categories;
+               add_category_func = e_m365_task_add_category;
+               break;
+       default:
+               g_warn_if_reached ();
+               return;
+       }
 
        ecb_m365_extract_categories (new_comp, NULL, &new_value);
        ecb_m365_extract_categories (old_comp, &old_value, NULL);
@@ -453,19 +570,19 @@ ecb_m365_add_categories (ECalBackendM365 *cbm365,
                }
 
                if (!same) {
-                       e_m365_event_begin_categories (builder);
+                       begin_categories_func (builder);
 
                        for (link = new_value; link; link = g_slist_next (link)) {
                                const gchar *category = link->data;
 
-                               e_m365_event_add_category (builder, category);
+                               add_category_func (builder, category);
                        }
 
-                       e_m365_event_end_categories (builder);
+                       end_categories_func (builder);
                }
        } else {
-               e_m365_event_begin_categories (builder);
-               e_m365_event_end_categories (builder);
+               begin_categories_func (builder);
+               end_categories_func (builder);
        }
 
        if (new_value)
@@ -476,13 +593,23 @@ ecb_m365_add_categories (ECalBackendM365 *cbm365,
 
 static void
 ecb_m365_get_subject (ECalBackendM365 *cbm365,
-                     EM365Event *m365_event,
+                     EM365Event *m365_object,
                      ICalComponent *inout_comp,
                      ICalPropertyKind prop_kind)
 {
        const gchar *subject;
 
-       subject = e_m365_event_get_subject (m365_event);
+       switch (i_cal_component_isa (inout_comp)) {
+       case I_CAL_VEVENT_COMPONENT:
+               subject = e_m365_event_get_subject (m365_object);
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               subject = e_m365_task_get_subject (m365_object);
+               break;
+       default:
+               g_warn_if_reached ();
+               return;
+       }
 
        if (subject)
                i_cal_component_set_summary (inout_comp, subject);
@@ -500,20 +627,42 @@ ecb_m365_add_subject (ECalBackendM365 *cbm365,
        new_value = i_cal_component_get_summary (new_comp);
        old_value = old_comp ? i_cal_component_get_summary (old_comp) : NULL;
 
-       if (g_strcmp0 (new_value, old_value) != 0)
-               e_m365_event_add_subject (builder, new_value ? new_value : "");
+       if (g_strcmp0 (new_value, old_value) != 0) {
+               switch (i_cal_component_isa (new_comp)) {
+               case I_CAL_VEVENT_COMPONENT:
+                       e_m365_event_add_subject (builder, new_value ? new_value : "");
+                       break;
+               case I_CAL_VTODO_COMPONENT:
+                       e_m365_task_add_subject (builder, new_value ? new_value : "");
+                       break;
+               default:
+                       g_warn_if_reached ();
+                       return;
+               }
+       }
 }
 
 static void
 ecb_m365_get_body (ECalBackendM365 *cbm365,
-                  EM365Event *m365_event,
+                  JsonObject *m365_object,
                   ICalComponent *inout_comp,
                   ICalPropertyKind prop_kind)
 {
        EM365ItemBody *value;
        const gchar *content;
 
-       value = e_m365_event_get_body (m365_event);
+       switch (i_cal_component_isa (inout_comp)) {
+       case I_CAL_VEVENT_COMPONENT:
+               value = e_m365_event_get_body (m365_object);
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               value = e_m365_task_get_body (m365_object);
+               break;
+       default:
+               g_warn_if_reached ();
+               return;
+       }
+
        content = value ? e_m365_item_body_get_content (value) : NULL;
 
        if (content && *content && strcmp (content, "\r\n") != 0)
@@ -532,20 +681,41 @@ ecb_m365_add_body (ECalBackendM365 *cbm365,
        new_value = i_cal_component_get_description (new_comp);
        old_value = old_comp ? i_cal_component_get_description (old_comp) : NULL;
 
-       if (g_strcmp0 (new_value, old_value) != 0)
-               e_m365_event_add_body (builder, E_M365_ITEM_BODY_CONTENT_TYPE_TEXT, new_value);
+       if (g_strcmp0 (new_value, old_value) != 0) {
+               switch (i_cal_component_isa (new_comp)) {
+               case I_CAL_VEVENT_COMPONENT:
+                       e_m365_event_add_body (builder, E_M365_ITEM_BODY_CONTENT_TYPE_TEXT, new_value);
+                       break;
+               case I_CAL_VTODO_COMPONENT:
+                       e_m365_task_add_body (builder, E_M365_ITEM_BODY_CONTENT_TYPE_TEXT, new_value);
+                       break;
+               default:
+                       g_warn_if_reached ();
+                       return;
+               }
+       }
 }
 
 static void
 ecb_m365_get_sensitivity (ECalBackendM365 *cbm365,
-                         EM365Event *m365_event,
+                         JsonObject *m365_object,
                          ICalComponent *inout_comp,
                          ICalPropertyKind prop_kind)
 {
        EM365SensitivityType value;
        ICalProperty_Class cls = I_CAL_CLASS_NONE;
 
-       value = e_m365_event_get_sensitivity (m365_event);
+       switch (i_cal_component_isa (inout_comp)) {
+       case I_CAL_VEVENT_COMPONENT:
+               value = e_m365_event_get_sensitivity (m365_object);
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               value = e_m365_task_get_sensitivity (m365_object);
+               break;
+       default:
+               g_warn_if_reached ();
+               return;
+       }
 
        if (value == E_M365_SENSITIVITY_NORMAL)
                cls = I_CAL_CLASS_PUBLIC;
@@ -592,7 +762,17 @@ ecb_m365_add_sensitivity (ECalBackendM365 *cbm365,
                else if (new_value == I_CAL_CLASS_CONFIDENTIAL)
                        value = E_M365_SENSITIVITY_CONFIDENTIAL;
 
-               e_m365_event_add_sensitivity (builder, value);
+               switch (i_cal_component_isa (new_comp)) {
+               case I_CAL_VEVENT_COMPONENT:
+                       e_m365_event_add_sensitivity (builder, value);
+                       break;
+               case I_CAL_VTODO_COMPONENT:
+                       e_m365_task_add_sensitivity (builder, value);
+                       break;
+               default:
+                       g_warn_if_reached ();
+                       return;
+               }
        }
 }
 
@@ -1114,10 +1294,10 @@ ecb_m365_add_importance (ECalBackendM365 *cbm365,
 }
 
 static void
-ecb_m365_get_status (ECalBackendM365 *cbm365,
-                    EM365Event *m365_event,
-                    ICalComponent *inout_comp,
-                    ICalPropertyKind prop_kind)
+ecb_m365_get_event_status (ECalBackendM365 *cbm365,
+                          EM365Event *m365_event,
+                          ICalComponent *inout_comp,
+                          ICalPropertyKind prop_kind)
 {
        ICalPropertyStatus status = I_CAL_STATUS_NONE;
 
@@ -1310,7 +1490,7 @@ ecb_m365_add_days_of_week_from_ical (JsonBuilder *builder,
 
 static gboolean
 ecb_m365_get_recurrence (ECalBackendM365 *cbm365,
-                        EM365Event *m365_event,
+                        JsonObject *m365_object,
                         ICalComponent *inout_comp,
                         ICalPropertyKind prop_kind,
                         GCancellable *cancellable,
@@ -1323,7 +1503,18 @@ ecb_m365_get_recurrence (ECalBackendM365 *cbm365,
        ICalRecurrenceWeekday week_day;
        gint month;
 
-       m365_recr = e_m365_event_get_recurrence (m365_event);
+       switch (i_cal_component_isa (inout_comp)) {
+       case I_CAL_VEVENT_COMPONENT:
+               m365_recr = e_m365_event_get_recurrence (m365_object);
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               m365_recr = e_m365_task_get_recurrence (m365_object);
+               break;
+       default:
+               g_warn_if_reached ();
+               return FALSE;
+       }
+
        m365_pattern = m365_recr ? e_m365_patterned_recurrence_get_pattern (m365_recr) : NULL;
        m365_range = m365_recr ? e_m365_patterned_recurrence_get_range (m365_recr) : NULL;
 
@@ -1447,6 +1638,25 @@ ecb_m365_add_recurrence (ECalBackendM365 *cbm365,
 {
        ICalProperty *new_value, *old_value;
        gboolean success = TRUE;
+       void (* begin_recurrence_func) (JsonBuilder *builder);
+       void (* end_recurrence_func) (JsonBuilder *builder);
+       void (* add_null_recurrence_func) (JsonBuilder *builder);
+
+       switch (i_cal_component_isa (new_comp)) {
+       case I_CAL_VEVENT_COMPONENT:
+               begin_recurrence_func = e_m365_event_begin_recurrence;
+               end_recurrence_func = e_m365_event_end_recurrence;
+               add_null_recurrence_func = e_m365_event_add_null_recurrence;
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               begin_recurrence_func = e_m365_task_begin_recurrence;
+               end_recurrence_func = e_m365_task_end_recurrence;
+               add_null_recurrence_func = e_m365_task_add_null_recurrence;
+               break;
+       default:
+               g_warn_if_reached ();
+               return FALSE;
+       }
 
        if (i_cal_component_count_properties (new_comp, prop_kind) > 1) {
                g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_NOT_SUPPORTED,
@@ -1501,7 +1711,7 @@ ecb_m365_add_recurrence (ECalBackendM365 *cbm365,
                        ICalTime *dtstart;
                        gint by_pos, month, yy = 0, mm = 0, dd = 0;
 
-                       e_m365_event_begin_recurrence (builder);
+                       begin_recurrence_func (builder);
                        e_m365_patterned_recurrence_begin_pattern (builder);
 
                        switch (i_cal_recurrence_get_freq (new_rrule)) {
@@ -1607,12 +1817,12 @@ ecb_m365_add_recurrence (ECalBackendM365 *cbm365,
                        }
 
                        e_m365_patterned_recurrence_end_range (builder);
-                       e_m365_event_end_recurrence (builder);
+                       end_recurrence_func (builder);
                }
 
                g_clear_object (&new_rrule);
        } else {
-               e_m365_event_add_null_recurrence (builder);
+               add_null_recurrence_func (builder);
        }
 
        g_clear_object (&new_value);
@@ -1623,30 +1833,78 @@ ecb_m365_add_recurrence (ECalBackendM365 *cbm365,
 
 static gboolean
 ecb_m365_get_reminder (ECalBackendM365 *cbm365,
-                      EM365Event *m365_event,
+                      EM365Event *m365_object,
                       ICalComponent *inout_comp,
                       ICalPropertyKind prop_kind,
                       GCancellable *cancellable,
                       GError **error)
 {
-       if (e_m365_event_get_is_reminder_on (m365_event)) {
-               ECalComponentAlarm *alarm;
-               ECalComponentAlarmTrigger *trigger;
-               ICalDuration *duration;
+       switch (i_cal_component_isa (inout_comp)) {
+       case I_CAL_VEVENT_COMPONENT:
+               if (e_m365_event_get_is_reminder_on (m365_object)) {
+                       ECalComponentAlarm *alarm;
+                       ECalComponentAlarmTrigger *trigger;
+                       ICalDuration *duration;
+
+                       duration = i_cal_duration_new_from_int (-60 * 
e_m365_event_get_reminder_minutes_before_start (m365_object));
+                       trigger = e_cal_component_alarm_trigger_new_relative 
(E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START, duration);
+                       g_object_unref (duration);
+
+                       alarm = e_cal_component_alarm_new ();
+                       e_cal_component_alarm_set_action (alarm, E_CAL_COMPONENT_ALARM_DISPLAY);
+                       e_cal_component_alarm_take_summary (alarm, e_cal_component_text_new 
(e_m365_event_get_subject (m365_object), NULL));
+                       e_cal_component_alarm_take_description (alarm, e_cal_component_text_new 
(e_m365_event_get_subject (m365_object), NULL));
+                       e_cal_component_alarm_take_trigger (alarm, trigger);
+
+                       i_cal_component_take_component (inout_comp, e_cal_component_alarm_get_as_component 
(alarm));
+
+                       e_cal_component_alarm_free (alarm);
+               }
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               if (e_m365_task_get_is_reminder_on (m365_object)) {
+                       EM365DateTimeWithZone *reminder_dt;
 
-               duration = i_cal_duration_new_from_int (-60 * e_m365_event_get_reminder_minutes_before_start 
(m365_event));
-               trigger = e_cal_component_alarm_trigger_new_relative 
(E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START, duration);
-               g_object_unref (duration);
+                       reminder_dt = e_m365_task_get_reminder_date_time (m365_object);
 
-               alarm = e_cal_component_alarm_new ();
-               e_cal_component_alarm_set_action (alarm, E_CAL_COMPONENT_ALARM_DISPLAY);
-               e_cal_component_alarm_take_summary (alarm, e_cal_component_text_new (e_m365_event_get_subject 
(m365_event), NULL));
-               e_cal_component_alarm_take_description (alarm, e_cal_component_text_new 
(e_m365_event_get_subject (m365_event), NULL));
-               e_cal_component_alarm_take_trigger (alarm, trigger);
+                       if (reminder_dt) {
+                               ECalComponentAlarm *alarm;
+                               ECalComponentAlarmTrigger *trigger;
+                               ICalTimezone *tz;
+                               ICalTime *itt;
+                               time_t tt;
+                               const gchar *zone;
+
+                               tt = e_m365_date_time_get_date_time (reminder_dt);
+                               zone = e_m365_date_time_get_time_zone (reminder_dt);
+
+                               if (zone && *zone)
+                                       zone = e_m365_tz_utils_get_ical_equivalent (zone);
+
+                               tz = zone && *zone ? ecb_m365_get_timezone_sync (cbm365, zone) : NULL;
 
-               i_cal_component_take_component (inout_comp, e_cal_component_alarm_get_as_component (alarm));
+                               if (!tz)
+                                       tz = i_cal_timezone_get_utc_timezone ();
 
-               e_cal_component_alarm_free (alarm);
+                               itt = i_cal_time_new_from_timet_with_zone (tt, FALSE, tz);
+                               trigger = e_cal_component_alarm_trigger_new_absolute (itt);
+                               g_object_unref (itt);
+
+                               alarm = e_cal_component_alarm_new ();
+                               e_cal_component_alarm_set_action (alarm, E_CAL_COMPONENT_ALARM_DISPLAY);
+                               e_cal_component_alarm_take_summary (alarm, e_cal_component_text_new 
(e_m365_task_get_subject (m365_object), NULL));
+                               e_cal_component_alarm_take_description (alarm, e_cal_component_text_new 
(e_m365_task_get_subject (m365_object), NULL));
+                               e_cal_component_alarm_take_trigger (alarm, trigger);
+
+                               i_cal_component_take_component (inout_comp, 
e_cal_component_alarm_get_as_component (alarm));
+
+                               e_cal_component_alarm_free (alarm);
+                       }
+               }
+               break;
+       default:
+               g_warn_if_reached ();
+               return FALSE;
        }
 
        return TRUE;
@@ -1679,22 +1937,46 @@ ecb_m365_add_reminder (ECalBackendM365 *cbm365,
        if (new_value) {
                ECalComponentAlarm *new_alarm;
                ECalComponentAlarmTrigger *new_trigger;
+               ICalComponentKind kind;
                ICalDuration *new_duration = NULL;
+               ICalTime *new_absolute_time = NULL;
                gboolean changed = TRUE;
 
+               kind = i_cal_component_isa (new_comp);
+
                new_alarm = e_cal_component_alarm_new_from_component (new_value);
                new_trigger = new_alarm ? e_cal_component_alarm_get_trigger (new_alarm) : NULL;
 
-               success = new_trigger && e_cal_component_alarm_trigger_get_kind (new_trigger) == 
E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START;
+               switch (kind) {
+               case I_CAL_VEVENT_COMPONENT:
+                       success = new_trigger && e_cal_component_alarm_trigger_get_kind (new_trigger) == 
E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START;
+                       if (success) {
+                               new_duration = e_cal_component_alarm_trigger_get_duration (new_trigger);
 
-               if (success) {
-                       new_duration = e_cal_component_alarm_trigger_get_duration (new_trigger);
+                               success = new_duration && i_cal_duration_as_int (new_duration) <= 0;
+                       }
 
-                       success = new_duration && i_cal_duration_as_int (new_duration) <= 0;
-               }
+                       if (!success) {
+                               g_propagate_error (error, ECC_ERROR_EX (E_CAL_CLIENT_ERROR_INVALID_OBJECT, 
_("Microsoft 365 event can have only a reminder before event start")));
+                       }
+                       break;
+               case I_CAL_VTODO_COMPONENT:
+                       success = new_trigger && e_cal_component_alarm_trigger_get_kind (new_trigger) == 
E_CAL_COMPONENT_ALARM_TRIGGER_ABSOLUTE;
 
-               if (!success) {
-                       g_propagate_error (error, ECC_ERROR_EX (E_CAL_CLIENT_ERROR_INVALID_OBJECT, 
_("Microsoft 365 calendar can store only a reminder before event start")));
+                       if (success) {
+                               new_absolute_time = e_cal_component_alarm_trigger_get_absolute_time 
(new_trigger);
+
+                               success = new_absolute_time != NULL;
+                       }
+
+                       if (!success) {
+                               g_propagate_error (error, ECC_ERROR_EX (E_CAL_CLIENT_ERROR_INVALID_OBJECT, 
_("Microsoft 365 task can have only a reminder with absolute time")));
+                       }
+                       break;
+               default:
+                       g_warn_if_reached ();
+                       success = FALSE;
+                       break;
                }
 
                if (success && old_value && new_trigger) {
@@ -1709,10 +1991,24 @@ ecb_m365_add_reminder (ECalBackendM365 *cbm365,
 
                                if (!changed) {
                                        ICalDuration *old_duration;
-
-                                       old_duration = e_cal_component_alarm_trigger_get_duration 
(old_trigger);
-
-                                       changed = !old_duration || i_cal_duration_as_int (new_duration) != 
i_cal_duration_as_int (old_duration);
+                                       ICalTime *old_absolute_time;
+
+                                       switch (kind) {
+                                       case I_CAL_VEVENT_COMPONENT:
+                                               old_duration = e_cal_component_alarm_trigger_get_duration 
(old_trigger);
+
+                                               changed = !old_duration || i_cal_duration_as_int 
(new_duration) != i_cal_duration_as_int (old_duration);
+                                               break;
+                                       case I_CAL_VTODO_COMPONENT:
+                                               old_absolute_time = 
e_cal_component_alarm_trigger_get_absolute_time (old_trigger);
+
+                                               changed = !old_absolute_time || i_cal_time_compare 
(new_absolute_time, old_absolute_time) != 0;
+                                               break;
+                                       default:
+                                               g_warn_if_reached ();
+                                               changed = FALSE;
+                                               break;
+                                       }
                                }
                        }
 
@@ -1720,13 +2016,45 @@ ecb_m365_add_reminder (ECalBackendM365 *cbm365,
                }
 
                if (success && changed) {
-                       e_m365_event_add_is_reminder_on (builder, TRUE);
-                       e_m365_event_add_reminder_minutes_before_start (builder, i_cal_duration_as_int 
(new_duration) / -60);
+                       ICalTimezone *izone = NULL;
+                       const gchar *wzone = NULL;
+                       time_t tt;
+
+                       switch (kind) {
+                       case I_CAL_VEVENT_COMPONENT:
+                               e_m365_event_add_is_reminder_on (builder, TRUE);
+                               e_m365_event_add_reminder_minutes_before_start (builder, 
i_cal_duration_as_int (new_duration) / -60);
+                               break;
+                       case I_CAL_VTODO_COMPONENT:
+                               izone = i_cal_time_get_timezone (new_absolute_time);
+
+                               if (izone)
+                                       wzone = e_m365_tz_utils_get_msdn_equivalent 
(i_cal_timezone_get_location (izone));
+
+                               tt = i_cal_time_as_timet_with_zone (new_absolute_time, wzone ? NULL : izone);
+
+                               e_m365_task_add_is_reminder_on (builder, TRUE);
+                               e_m365_task_add_reminder_date_time (builder, tt, wzone);
+                               break;
+                       default:
+                               g_warn_if_reached ();
+                               break;
+                       }
                }
 
                e_cal_component_alarm_free (new_alarm);
        } else {
-               e_m365_event_add_is_reminder_on (builder, FALSE);
+               switch (i_cal_component_isa (new_comp)) {
+               case I_CAL_VEVENT_COMPONENT:
+                       e_m365_event_add_is_reminder_on (builder, FALSE);
+                       break;
+               case I_CAL_VTODO_COMPONENT:
+                       e_m365_task_add_is_reminder_on (builder, FALSE);
+                       break;
+               default:
+                       g_warn_if_reached ();
+                       break;
+               }
        }
 
        g_clear_object (&new_value);
@@ -1737,21 +2065,43 @@ ecb_m365_add_reminder (ECalBackendM365 *cbm365,
 
 static gboolean
 ecb_m365_get_attachments (ECalBackendM365 *cbm365,
-                         EM365Event *m365_event,
+                         JsonObject *m365_object,
                          ICalComponent *inout_comp,
                          ICalPropertyKind prop_kind,
                          GCancellable *cancellable,
                          GError **error)
 {
        GSList *attachments = NULL, *link;
+       const gchar *id;
        gboolean success = TRUE;
 
-       if (!e_m365_event_get_has_attachments (m365_event))
-               return TRUE;
+       switch (i_cal_component_isa (inout_comp)) {
+       case I_CAL_VEVENT_COMPONENT:
+               if (!e_m365_event_get_has_attachments (m365_object))
+                       return TRUE;
+
+               id = e_m365_event_get_id (m365_object);
+
+               if (!e_m365_connection_list_event_attachments_sync (cbm365->priv->cnc, NULL,
+                       cbm365->priv->group_id, cbm365->priv->folder_id, id, 
"id,name,contentType,contentBytes",
+                       &attachments, cancellable, error)) {
+                       return FALSE;
+               }
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               if (!e_m365_task_get_has_attachments (m365_object))
+                       return TRUE;
+
+               id = e_m365_task_get_id (m365_object);
 
-       if (!e_m365_connection_list_event_attachments_sync (cbm365->priv->cnc, NULL,
-               cbm365->priv->group_id, cbm365->priv->folder_id, e_m365_event_get_id (m365_event), 
"id,name,contentType,contentBytes",
-               &attachments, cancellable, error)) {
+               if (!e_m365_connection_list_task_attachments_sync (cbm365->priv->cnc, NULL,
+                       cbm365->priv->group_id, cbm365->priv->folder_id, id, 
"id,name,contentType,contentBytes",
+                       &attachments, cancellable, error)) {
+                       return FALSE;
+               }
+               break;
+       default:
+               g_warn_if_reached ();
                return FALSE;
        }
 
@@ -1764,7 +2114,7 @@ ecb_m365_get_attachments (ECalBackendM365 *cbm365,
                    !e_m365_attachment_get_name (m365_attach))
                        continue;
 
-               filename = g_build_filename (cbm365->priv->attachments_dir, e_m365_event_get_id (m365_event), 
e_m365_attachment_get_id (m365_attach), NULL);
+               filename = g_build_filename (cbm365->priv->attachments_dir, id, e_m365_attachment_get_id 
(m365_attach), NULL);
 
                content_stream = camel_stream_fs_new_with_name (filename, O_CREAT | O_TRUNC | O_WRONLY, 0666, 
error);
 
@@ -1896,8 +2246,39 @@ ecb_m365_add_attachments (ECalBackendM365 *cbm365,
 {
        GSList *new_attachs = NULL;
        GHashTable *old_attachs = NULL;
+       gboolean (* add_attachment_func) (EM365Connection *cnc,
+                                         const gchar *user_override,
+                                         const gchar *group_id,
+                                         const gchar *folder_id,
+                                         const gchar *item_id,
+                                         JsonBuilder *in_attachment,
+                                         EM365Attachment **out_attachment,
+                                         GCancellable *cancellable,
+                                         GError **error);
+       gboolean (* delete_attachment_func) (EM365Connection *cnc,
+                                            const gchar *user_override,
+                                            const gchar *group_id,
+                                            const gchar *folder_id,
+                                            const gchar *item_id,
+                                            const gchar *attachment_id,
+                                            GCancellable *cancellable,
+                                            GError **error);
        gboolean success = TRUE;
 
+       switch (i_cal_component_isa (new_comp)) {
+       case I_CAL_VEVENT_COMPONENT:
+               add_attachment_func = e_m365_connection_add_event_attachment_sync;
+               delete_attachment_func = e_m365_connection_delete_event_attachment_sync;
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               add_attachment_func = e_m365_connection_add_task_attachment_sync;
+               delete_attachment_func = e_m365_connection_delete_task_attachment_sync;
+               break;
+       default:
+               g_warn_if_reached ();
+               return FALSE;
+       }
+
        if (!i_cal_component_count_properties (new_comp, I_CAL_ATTACH_PROPERTY) &&
            !(old_comp ? i_cal_component_count_properties (old_comp, I_CAL_ATTACH_PROPERTY) : 0)) {
                return TRUE;
@@ -2046,7 +2427,7 @@ ecb_m365_add_attachments (ECalBackendM365 *cbm365,
 
                                e_m365_attachment_end_attachment (builder);
 
-                               success = e_m365_connection_add_event_attachment_sync (cbm365->priv->cnc, 
NULL,
+                               success = add_attachment_func (cbm365->priv->cnc, NULL,
                                        cbm365->priv->group_id, cbm365->priv->folder_id, m365_id,
                                        builder, NULL, cancellable, error);
 
@@ -2069,7 +2450,7 @@ ecb_m365_add_attachments (ECalBackendM365 *cbm365,
                while (g_hash_table_iter_next (&iter, &key, NULL) && success) {
                        const gchar *attachment_id = key;
 
-                       success = e_m365_connection_delete_event_attachment_sync (cbm365->priv->cnc, NULL,
+                       success = delete_attachment_func (cbm365->priv->cnc, NULL,
                                cbm365->priv->group_id, cbm365->priv->folder_id, i_cal_component_get_uid 
(new_comp),
                                attachment_id, cancellable, error);
                }
@@ -2082,6 +2463,82 @@ ecb_m365_add_attachments (ECalBackendM365 *cbm365,
        return success;
 }
 
+static void
+ecb_m365_get_task_status (ECalBackendM365 *cbm365,
+                         EM365Task *m365_task,
+                         ICalComponent *inout_comp,
+                         ICalPropertyKind prop_kind)
+{
+       ICalPropertyStatus status = I_CAL_STATUS_NONE;
+
+       switch (e_m365_task_get_status (m365_task)) {
+       case E_M365_STATUS_NOT_STARTED:
+               break;
+       case E_M365_STATUS_IN_PROGRESS:
+       case E_M365_STATUS_WAITING_ON_OTHERS:
+               status = I_CAL_STATUS_INPROCESS;
+               break;
+       case E_M365_STATUS_COMPLETED:
+               status = I_CAL_STATUS_COMPLETED;
+               break;
+       case E_M365_STATUS_DEFERRED:
+               status = I_CAL_STATUS_CANCELLED;
+               break;
+       default:
+               break;
+       }
+
+       if (status != I_CAL_STATUS_NONE)
+               i_cal_component_take_property (inout_comp, i_cal_property_new_status (status));
+}
+
+static void
+ecb_m365_add_task_status (ECalBackendM365 *cbm365,
+                         ICalComponent *new_comp,
+                         ICalComponent *old_comp,
+                         ICalPropertyKind prop_kind,
+                         JsonBuilder *builder)
+{
+       ICalProperty *new_prop, *old_prop;
+       ICalPropertyStatus new_value, old_value;
+
+       new_prop = i_cal_component_get_first_property (new_comp, prop_kind);
+       old_prop = old_comp ? i_cal_component_get_first_property (old_comp, prop_kind) : NULL;
+
+       if (!new_prop && !old_prop)
+               return;
+
+       new_value = new_prop ? i_cal_property_get_status (new_prop) : I_CAL_STATUS_NONE;
+       old_value = old_prop ? i_cal_property_get_status (old_prop) : I_CAL_STATUS_NONE;
+
+       if (new_value != old_value) {
+               EM365StatusType value = E_M365_STATUS_UNKNOWN;
+
+               switch (new_value) {
+               case I_CAL_STATUS_NONE:
+                       value = E_M365_STATUS_NOT_STARTED;
+                       break;
+               case I_CAL_STATUS_INPROCESS:
+                       value = E_M365_STATUS_IN_PROGRESS;
+                       break;
+               case I_CAL_STATUS_COMPLETED:
+                       value = E_M365_STATUS_COMPLETED;
+                       break;
+               case I_CAL_STATUS_CANCELLED:
+                       value = E_M365_STATUS_DEFERRED;
+                       break;
+               default:
+                       break;
+               }
+
+               if (value != E_M365_STATUS_UNKNOWN)
+                       e_m365_task_add_status (builder, value);
+       }
+
+       g_clear_object (&new_prop);
+       g_clear_object (&old_prop);
+}
+
 #define SIMPLE_FIELD(propknd, getfn, addfn) { propknd, FALSE, getfn, NULL, addfn, NULL }
 #define COMPLEX_FIELD(propknd, getfn, addfn) { propknd, FALSE, NULL, getfn, NULL, addfn }
 #define COMPLEX_FIELD_2(propknd, getfn, addfn) { propknd, TRUE, NULL, getfn, NULL, addfn }
@@ -2112,7 +2569,7 @@ struct _mappings {
                                                 JsonBuilder *builder,
                                                 GCancellable *cancellable,
                                                 GError **error);
-} mappings[] = {
+} event_mappings[] = {
        SIMPLE_FIELD    (I_CAL_UID_PROPERTY,            ecb_m365_get_uid,               NULL),
        SIMPLE_FIELD    (I_CAL_CREATED_PROPERTY,        ecb_m365_get_date_time,         NULL),
        SIMPLE_FIELD    (I_CAL_LASTMODIFIED_PROPERTY,   ecb_m365_get_date_time,         NULL),
@@ -2127,12 +2584,50 @@ struct _mappings {
        SIMPLE_FIELD    (I_CAL_ORGANIZER_PROPERTY,      ecb_m365_get_organizer,         
ecb_m365_add_organizer),
        SIMPLE_FIELD    (I_CAL_ATTENDEE_PROPERTY,       ecb_m365_get_attendees,         
ecb_m365_add_attendees),
        SIMPLE_FIELD    (I_CAL_PRIORITY_PROPERTY,       ecb_m365_get_importance,        
ecb_m365_add_importance),
-       SIMPLE_FIELD    (I_CAL_STATUS_PROPERTY,         ecb_m365_get_status,            NULL),
+       SIMPLE_FIELD    (I_CAL_STATUS_PROPERTY,         ecb_m365_get_event_status,      NULL),
+       COMPLEX_FIELD   (I_CAL_RRULE_PROPERTY,          ecb_m365_get_recurrence,        
ecb_m365_add_recurrence),
+       COMPLEX_FIELD   (I_CAL_X_PROPERTY,              ecb_m365_get_reminder,          
ecb_m365_add_reminder),
+       COMPLEX_FIELD_2 (I_CAL_ATTACH_PROPERTY,         ecb_m365_get_attachments,       
ecb_m365_add_attachments)
+}, task_mappings[] = {
+       SIMPLE_FIELD    (I_CAL_UID_PROPERTY,            ecb_m365_get_uid,               NULL),
+       SIMPLE_FIELD    (I_CAL_CREATED_PROPERTY,        ecb_m365_get_date_time,         NULL),
+       SIMPLE_FIELD    (I_CAL_LASTMODIFIED_PROPERTY,   ecb_m365_get_date_time,         NULL),
+       SIMPLE_FIELD    (I_CAL_DTSTART_PROPERTY,        ecb_m365_get_date_time_zone,    
ecb_m365_add_date_time_zone),
+       SIMPLE_FIELD    (I_CAL_DUE_PROPERTY,            ecb_m365_get_date_time_zone,    
ecb_m365_add_date_time_zone),
+       SIMPLE_FIELD    (I_CAL_COMPLETED_PROPERTY,      ecb_m365_get_date_time_zone,    
ecb_m365_add_date_time_zone),
+       SIMPLE_FIELD    (I_CAL_CATEGORIES_PROPERTY,     ecb_m365_get_categories,        
ecb_m365_add_categories),
+       SIMPLE_FIELD    (I_CAL_SUMMARY_PROPERTY,        ecb_m365_get_subject,           ecb_m365_add_subject),
+       SIMPLE_FIELD    (I_CAL_DESCRIPTION_PROPERTY,    ecb_m365_get_body,              ecb_m365_add_body),
+       SIMPLE_FIELD    (I_CAL_CLASS_PROPERTY,          ecb_m365_get_sensitivity,       
ecb_m365_add_sensitivity),
+       SIMPLE_FIELD    (I_CAL_STATUS_PROPERTY,         ecb_m365_get_task_status,       
ecb_m365_add_task_status),
        COMPLEX_FIELD   (I_CAL_RRULE_PROPERTY,          ecb_m365_get_recurrence,        
ecb_m365_add_recurrence),
        COMPLEX_FIELD   (I_CAL_X_PROPERTY,              ecb_m365_get_reminder,          
ecb_m365_add_reminder),
        COMPLEX_FIELD_2 (I_CAL_ATTACH_PROPERTY,         ecb_m365_get_attachments,       
ecb_m365_add_attachments)
 };
 
+static const struct _mappings *
+ecb_m365_get_mappings_for_backend (ECalBackendM365 *cbm365,
+                                  guint *out_n_elements)
+{
+       ICalComponentKind kind;
+
+       kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbm365));
+
+       if (kind == I_CAL_VEVENT_COMPONENT) {
+               *out_n_elements = G_N_ELEMENTS (event_mappings);
+               return event_mappings;
+       }
+
+       if (kind == I_CAL_VTODO_COMPONENT) {
+               *out_n_elements = G_N_ELEMENTS (task_mappings);
+               return task_mappings;
+       }
+
+       g_warn_if_reached ();
+
+       return NULL;
+}
+
 static gchar *
 ecb_m365_join_to_extra (const gchar *change_key,
                        const gchar *ical_comp)
@@ -2145,23 +2640,38 @@ ecb_m365_join_to_extra (const gchar *change_key,
 
 static ICalComponent *
 ecb_m365_json_to_ical (ECalBackendM365 *cbm365,
-                      EM365Event *m365_event,
+                      JsonObject *m365_object,
                       GCancellable *cancellable,
                       GError **error)
 {
-       ICalComponent *icomp;
-       gint ii;
+       const struct _mappings *mappings;
+       ICalComponent *icomp = NULL;
+       ICalComponentKind kind;
+       guint ii, n_elements = 0;
        gboolean success = TRUE;
 
-       g_return_val_if_fail (m365_event != NULL, NULL);
+       g_return_val_if_fail (m365_object != NULL, NULL);
 
-       icomp = i_cal_component_new_vevent ();
+       mappings = ecb_m365_get_mappings_for_backend (cbm365, &n_elements);
+       g_return_val_if_fail (mappings != NULL, NULL);
 
-       for (ii = 0; success && ii < G_N_ELEMENTS (mappings); ii++) {
+       kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbm365));
+
+       if (kind == I_CAL_VEVENT_COMPONENT)
+               icomp = i_cal_component_new_vevent ();
+       else if (kind == I_CAL_VTODO_COMPONENT)
+               icomp = i_cal_component_new_vtodo ();
+       else
+               g_warn_if_reached ();
+
+       if (!icomp)
+               return NULL;
+
+       for (ii = 0; success && ii < n_elements; ii++) {
                if (mappings[ii].get_simple_func) {
-                       mappings[ii].get_simple_func (cbm365, m365_event, icomp, mappings[ii].prop_kind);
+                       mappings[ii].get_simple_func (cbm365, m365_object, icomp, mappings[ii].prop_kind);
                } else if (mappings[ii].get_func) {
-                       success = mappings[ii].get_func (cbm365, m365_event, icomp, mappings[ii].prop_kind, 
cancellable, error);
+                       success = mappings[ii].get_func (cbm365, m365_object, icomp, mappings[ii].prop_kind, 
cancellable, error);
                }
        }
 
@@ -2206,16 +2716,20 @@ ecb_m365_ical_to_json_locked (ECalBackendM365 *cbm365,
                              GCancellable *cancellable,
                              GError **error)
 {
+       const struct _mappings *mappings;
        JsonBuilder *builder;
-       gint ii;
+       guint ii, n_elements = 0;
        gboolean success = TRUE;
 
        g_return_val_if_fail (new_comp != NULL, NULL);
 
+       mappings = ecb_m365_get_mappings_for_backend (cbm365, &n_elements);
+       g_return_val_if_fail (mappings != NULL, NULL);
+
        builder = json_builder_new_immutable ();
        e_m365_json_begin_object_member (builder, NULL);
 
-       for (ii = 0; success && ii < G_N_ELEMENTS (mappings); ii++) {
+       for (ii = 0; success && ii < n_elements; ii++) {
                if (mappings[ii].add_simple_func) {
                        mappings[ii].add_simple_func (cbm365, new_comp, old_comp, mappings[ii].prop_kind, 
builder);
                } else if (!mappings[ii].add_in_second_go && mappings[ii].add_func) {
@@ -2239,12 +2753,16 @@ ecb_m365_ical_to_json_2nd_go_locked (ECalBackendM365 *cbm365,
                                     GCancellable *cancellable,
                                     GError **error)
 {
-       gint ii;
+       const struct _mappings *mappings;
+       guint ii, n_elements = 0;
        gboolean success = TRUE;
 
        g_return_val_if_fail (new_comp != NULL, FALSE);
 
-       for (ii = 0; success && ii < G_N_ELEMENTS (mappings); ii++) {
+       mappings = ecb_m365_get_mappings_for_backend (cbm365, &n_elements);
+       g_return_val_if_fail (mappings != NULL, FALSE);
+
+       for (ii = 0; success && ii < n_elements; ii++) {
                if (mappings[ii].add_in_second_go && mappings[ii].add_func) {
                        success = mappings[ii].add_func (cbm365, new_comp, old_comp, mappings[ii].prop_kind, 
m365_id, NULL, cancellable, error);
                }
@@ -2254,34 +2772,45 @@ ecb_m365_ical_to_json_2nd_go_locked (ECalBackendM365 *cbm365,
 }
 
 static gboolean
-ecb_m365_download_event_changes_locked (ECalBackendM365 *cbm365,
-                                       const GSList *ids,
-                                       GSList **out_info_objects,
-                                       GCancellable *cancellable,
-                                       GError **error)
+ecb_m365_download_changes_locked (ECalBackendM365 *cbm365,
+                                 const GSList *ids,
+                                 GSList **out_info_objects,
+                                 GCancellable *cancellable,
+                                 GError **error)
 {
-       GSList *events = NULL, *link;
+       GSList *items = NULL, *link;
 
        if (!ids)
                return TRUE;
 
-       if (!e_m365_connection_get_events_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id, 
cbm365->priv->folder_id, ids, NULL, NULL, &events, cancellable, error))
+       switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbm365))) {
+       case I_CAL_VEVENT_COMPONENT:
+               if (!e_m365_connection_get_events_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id, 
cbm365->priv->folder_id, ids, NULL, NULL, &items, cancellable, error))
+                       return FALSE;
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               if (!e_m365_connection_get_tasks_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id, 
cbm365->priv->folder_id, ids, NULL, NULL, &items, cancellable, error))
+                       return FALSE;
+               break;
+       default:
+               g_warn_if_reached ();
                return FALSE;
+       }
 
-       for (link = events; link; link = g_slist_next (link)) {
-               EM365Event *event = link->data;
+       for (link = items; link; link = g_slist_next (link)) {
+               JsonObject *item = link->data;
                ECalMetaBackendInfo *nfo;
 
-               if (!event)
+               if (!item)
                        continue;
 
-               nfo = ecb_m365_json_to_ical_nfo (cbm365, event, cancellable, error);
+               nfo = ecb_m365_json_to_ical_nfo (cbm365, item, cancellable, error);
 
                if (nfo)
                        *out_info_objects = g_slist_prepend (*out_info_objects, nfo);
        }
 
-       g_slist_free_full (events, (GDestroyNotify) json_object_unref);
+       g_slist_free_full (items, (GDestroyNotify) json_object_unref);
 
        return TRUE;
 }
@@ -2335,11 +2864,24 @@ ecb_m365_connect_sync (ECalMetaBackend *meta_backend,
                       GError **error)
 {
        ECalBackendM365 *cbm365;
+       EM365FolderKind folder_kind;
        gboolean success = FALSE;
 
        g_return_val_if_fail (E_IS_CAL_BACKEND_M365 (meta_backend), FALSE);
        g_return_val_if_fail (out_auth_result != NULL, FALSE);
 
+       switch (e_cal_backend_get_kind (E_CAL_BACKEND (meta_backend))) {
+       case I_CAL_VEVENT_COMPONENT:
+               folder_kind = E_M365_FOLDER_KIND_CALENDAR;
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               folder_kind = E_M365_FOLDER_KIND_TASKS;
+               break;
+       default:
+               g_warn_if_reached ();
+               return FALSE;
+       }
+
        cbm365 = E_CAL_BACKEND_M365 (meta_backend);
 
        LOCK (cbm365);
@@ -2373,7 +2915,7 @@ ecb_m365_connect_sync (ECalMetaBackend *meta_backend,
                if (folder_id) {
                        cnc = e_m365_connection_new_for_backend (backend, registry, source, m365_settings);
 
-                       *out_auth_result = e_m365_connection_authenticate_sync (cnc, NULL, 
E_M365_FOLDER_KIND_CALENDAR, group_id, folder_id,
+                       *out_auth_result = e_m365_connection_authenticate_sync (cnc, NULL, folder_kind, 
group_id, folder_id,
                                out_certificate_pem, out_certificate_errors, cancellable, error);
 
                        if (*out_auth_result == E_SOURCE_AUTHENTICATION_ACCEPTED) {
@@ -2435,9 +2977,20 @@ ecb_m365_get_changes_sync (ECalMetaBackend *meta_backend,
 {
        ECalBackendM365 *cbm365;
        ECalCache *cal_cache;
-       GSList *events = NULL, *link;
+       GSList *items = NULL, *link;
        gboolean full_read;
        gboolean success = TRUE;
+       gboolean (* list_items_func) (EM365Connection *cnc,
+                                     const gchar *user_override,
+                                     const gchar *group_id,
+                                     const gchar *calendar_id,
+                                     const gchar *prefer_outlook_timezone,
+                                     const gchar *select,
+                                     GSList **out_items,
+                                     GCancellable *cancellable,
+                                     GError **error);
+       const gchar *(* get_id_func) (JsonObject *item);
+       const gchar *(* get_change_key_func) (JsonObject *item);
 
        g_return_val_if_fail (E_IS_CAL_BACKEND_M365 (meta_backend), FALSE);
        g_return_val_if_fail (out_new_sync_tag != NULL, FALSE);
@@ -2446,6 +2999,22 @@ ecb_m365_get_changes_sync (ECalMetaBackend *meta_backend,
        g_return_val_if_fail (out_modified_objects != NULL, FALSE);
        g_return_val_if_fail (out_removed_objects != NULL, FALSE);
 
+       switch (e_cal_backend_get_kind (E_CAL_BACKEND (meta_backend))) {
+       case I_CAL_VEVENT_COMPONENT:
+               list_items_func = e_m365_connection_list_events_sync;
+               get_id_func = e_m365_event_get_id;
+               get_change_key_func = e_m365_event_get_change_key;
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               list_items_func = e_m365_connection_list_tasks_sync;
+               get_id_func = e_m365_task_get_id;
+               get_change_key_func = e_m365_task_get_change_key;
+               break;
+       default:
+               g_warn_if_reached ();
+               return FALSE;
+       }
+
        *out_created_objects = NULL;
        *out_modified_objects = NULL;
        *out_removed_objects = NULL;
@@ -2459,23 +3028,23 @@ ecb_m365_get_changes_sync (ECalMetaBackend *meta_backend,
 
        full_read = !e_cache_get_count (E_CACHE (cal_cache), E_CACHE_INCLUDE_DELETED, cancellable, NULL);
 
-       success = e_m365_connection_list_events_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id, 
cbm365->priv->folder_id, NULL,
-               full_read ? NULL : "id,changeKey", &events, cancellable, error);
+       success = list_items_func (cbm365->priv->cnc, NULL, cbm365->priv->group_id, cbm365->priv->folder_id, 
NULL,
+               full_read ? NULL : "id,changeKey", &items, cancellable, error);
 
        if (success) {
-               GSList *new_ids = NULL; /* const gchar *, borrowed from 'events' objects */
-               GSList *changed_ids = NULL; /* const gchar *, borrowed from 'events' objects */
+               GSList *new_ids = NULL; /* const gchar *, borrowed from 'items' objects */
+               GSList *changed_ids = NULL; /* const gchar *, borrowed from 'items' objects */
 
-               for (link = events; link && !g_cancellable_is_cancelled (cancellable); link = g_slist_next 
(link)) {
-                       EM365Event *event = link->data;
+               for (link = items; link && !g_cancellable_is_cancelled (cancellable); link = g_slist_next 
(link)) {
+                       JsonObject *item = link->data;
                        const gchar *id, *change_key;
                        gchar *extra = NULL;
 
-                       if (!event)
+                       if (!item)
                                continue;
 
-                       id = e_m365_event_get_id (event);
-                       change_key = e_m365_event_get_change_key (event);
+                       id = get_id_func (item);
+                       change_key = get_change_key_func (item);
 
                        if (e_cal_cache_get_component_extra (cal_cache, id, NULL, &extra, cancellable, NULL)) 
{
                                const gchar *saved_change_key = NULL;
@@ -2488,7 +3057,7 @@ ecb_m365_get_changes_sync (ECalMetaBackend *meta_backend,
                                } else if (full_read) {
                                        ECalMetaBackendInfo *nfo;
 
-                                       nfo = ecb_m365_json_to_ical_nfo (cbm365, event, cancellable, NULL);
+                                       nfo = ecb_m365_json_to_ical_nfo (cbm365, item, cancellable, NULL);
 
                                        if (nfo)
                                                *out_modified_objects = g_slist_prepend 
(*out_modified_objects, nfo);
@@ -2500,7 +3069,7 @@ ecb_m365_get_changes_sync (ECalMetaBackend *meta_backend,
                        } else if (full_read) {
                                ECalMetaBackendInfo *nfo;
 
-                               nfo = ecb_m365_json_to_ical_nfo (cbm365, event, cancellable, NULL);
+                               nfo = ecb_m365_json_to_ical_nfo (cbm365, item, cancellable, NULL);
 
                                if (nfo)
                                        *out_created_objects = g_slist_prepend (*out_created_objects, nfo);
@@ -2511,19 +3080,19 @@ ecb_m365_get_changes_sync (ECalMetaBackend *meta_backend,
 
                if (new_ids) {
                        new_ids = g_slist_reverse (new_ids);
-                       success = ecb_m365_download_event_changes_locked (cbm365, new_ids, 
out_created_objects, cancellable, error);
+                       success = ecb_m365_download_changes_locked (cbm365, new_ids, out_created_objects, 
cancellable, error);
                }
 
                if (success && changed_ids) {
                        changed_ids = g_slist_reverse (changed_ids);
-                       success = ecb_m365_download_event_changes_locked (cbm365, changed_ids, 
out_modified_objects, cancellable, error);
+                       success = ecb_m365_download_changes_locked (cbm365, changed_ids, 
out_modified_objects, cancellable, error);
                }
 
                g_slist_free (new_ids);
                g_slist_free (changed_ids);
        }
 
-       g_slist_free_full (events, (GDestroyNotify) json_object_unref);
+       g_slist_free_full (items, (GDestroyNotify) json_object_unref);
 
        UNLOCK (cbm365);
 
@@ -2545,7 +3114,8 @@ ecb_m365_load_component_sync (ECalMetaBackend *meta_backend,
                              GError **error)
 {
        ECalBackendM365 *cbm365;
-       EM365Event *event = NULL;
+       JsonObject *item = NULL;
+       const gchar *(* get_change_key_func) (JsonObject *item);
        gboolean success;
 
        g_return_val_if_fail (E_IS_CAL_BACKEND_M365 (meta_backend), FALSE);
@@ -2557,18 +3127,31 @@ ecb_m365_load_component_sync (ECalMetaBackend *meta_backend,
 
        LOCK (cbm365);
 
-       success = e_m365_connection_get_event_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
-               cbm365->priv->folder_id, uid, NULL, NULL, &event, cancellable, error);
+       switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbm365))) {
+       case I_CAL_VEVENT_COMPONENT:
+               success = e_m365_connection_get_event_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
+                       cbm365->priv->folder_id, uid, NULL, NULL, &item, cancellable, error);
+               get_change_key_func = e_m365_event_get_change_key;
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               success = e_m365_connection_get_task_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
+                       cbm365->priv->folder_id, uid, NULL, NULL, &item, cancellable, error);
+               get_change_key_func = e_m365_task_get_change_key;
+               break;
+       default:
+               success = FALSE;
+               break;
+       }
 
        if (success) {
-               *out_component = ecb_m365_json_to_ical (cbm365, event, cancellable, error);
+               *out_component = ecb_m365_json_to_ical (cbm365, item, cancellable, error);
 
                if (*out_component) {
                        gchar *ical_str;
 
                        ical_str = i_cal_component_as_ical_string (*out_component);
 
-                       *out_extra = ecb_m365_join_to_extra (e_m365_event_get_change_key (event), ical_str);
+                       *out_extra = ecb_m365_join_to_extra (get_change_key_func (item), ical_str);
 
                        g_free (ical_str);
                } else {
@@ -2600,14 +3183,58 @@ ecb_m365_save_component_sync (ECalMetaBackend *meta_backend,
        ICalComponent *new_comp, *old_comp = NULL;
        JsonBuilder *builder;
        gboolean success = FALSE;
+       gboolean (* create_item_func) (EM365Connection *cnc,
+                                      const gchar *user_override,
+                                      const gchar *group_id,
+                                      const gchar *folder_id,
+                                      JsonBuilder *item,
+                                      JsonObject **out_created_item,
+                                      GCancellable *cancellable,
+                                      GError **error);
+       gboolean (* update_item_func) (EM365Connection *cnc,
+                                      const gchar *user_override,
+                                      const gchar *group_id,
+                                      const gchar *folder_id,
+                                      const gchar *item_id,
+                                      JsonBuilder *item,
+                                      GCancellable *cancellable,
+                                      GError **error);
+       const gchar *(* get_id_func) (JsonObject *item);
+       const gchar *(* get_change_key_func) (JsonObject *item);
+
 
        g_return_val_if_fail (E_IS_CAL_BACKEND_M365 (meta_backend), FALSE);
        g_return_val_if_fail (instances != NULL, FALSE);
 
-       if (instances->next) {
-               g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_NOT_SUPPORTED,
-                       _("Can store only simple events into Microsoft 365 calendar")));
+       switch (e_cal_backend_get_kind (E_CAL_BACKEND (meta_backend))) {
+       case I_CAL_VEVENT_COMPONENT:
+               if (instances->next) {
+                       g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_NOT_SUPPORTED,
+                               _("Can store only simple events into Microsoft 365 calendar")));
+
+                       return FALSE;
+               }
+
+               create_item_func = e_m365_connection_create_event_sync;
+               update_item_func = e_m365_connection_update_event_sync;
+               get_id_func = e_m365_event_get_id;
+               get_change_key_func = e_m365_event_get_change_key;
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               if (instances->next) {
+                       g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_NOT_SUPPORTED,
+                               _("Can store only simple tasks into Microsoft 365 task folder")));
+
+                       return FALSE;
+               }
 
+               create_item_func = e_m365_connection_create_task_sync;
+               update_item_func = e_m365_connection_update_task_sync;
+               get_id_func = e_m365_task_get_id;
+               get_change_key_func = e_m365_task_get_change_key;
+               break;
+       default:
+               g_warn_if_reached ();
                return FALSE;
        }
 
@@ -2632,7 +3259,7 @@ ecb_m365_save_component_sync (ECalMetaBackend *meta_backend,
                if (overwrite_existing) {
                        const gchar *uid = i_cal_component_get_uid (new_comp);
 
-                       success = e_m365_connection_update_event_sync (cbm365->priv->cnc, NULL, 
cbm365->priv->group_id,
+                       success = update_item_func (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
                                cbm365->priv->folder_id, uid, builder, cancellable, error);
 
                        if (success)
@@ -2643,30 +3270,30 @@ ecb_m365_save_component_sync (ECalMetaBackend *meta_backend,
                                *out_new_uid = g_strdup (uid);
                        }
                } else {
-                       EM365Event *created_event = NULL;
+                       JsonObject *created_item = NULL;
 
-                       success = e_m365_connection_create_event_sync (cbm365->priv->cnc, NULL, 
cbm365->priv->group_id,
-                               cbm365->priv->folder_id, builder, &created_event, cancellable, error);
+                       success = create_item_func (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
+                               cbm365->priv->folder_id, builder, &created_item, cancellable, error);
 
-                       if (success && created_event) {
-                               const gchar *m365_id = e_m365_event_get_id (created_event);
+                       if (success && created_item) {
+                               const gchar *m365_id = get_id_func (created_item);
 
                                success = ecb_m365_ical_to_json_2nd_go_locked (cbm365, new_comp, old_comp, 
m365_id, cancellable, error);
                        }
 
-                       if (success && created_event) {
+                       if (success && created_item) {
                                ICalComponent *icomp;
 
-                               *out_new_uid = g_strdup (e_m365_event_get_id (created_event));
+                               *out_new_uid = g_strdup (get_id_func (created_item));
 
-                               icomp = ecb_m365_json_to_ical (cbm365, created_event, cancellable, error);
+                               icomp = ecb_m365_json_to_ical (cbm365, created_item, cancellable, error);
 
                                if (icomp) {
                                        gchar *ical_str;
 
                                        ical_str = i_cal_component_as_ical_string (icomp);
 
-                                       *out_new_extra = ecb_m365_join_to_extra (e_m365_event_get_change_key 
(created_event), ical_str);
+                                       *out_new_extra = ecb_m365_join_to_extra (get_change_key_func 
(created_item), ical_str);
 
                                        g_clear_object (&icomp);
                                        g_free (ical_str);
@@ -2675,8 +3302,8 @@ ecb_m365_save_component_sync (ECalMetaBackend *meta_backend,
                                }
                        }
 
-                       if (created_event)
-                               json_object_unref (created_event);
+                       if (created_item)
+                               json_object_unref (created_item);
                }
 
                g_clear_object (&builder);
@@ -2703,7 +3330,7 @@ ecb_m365_remove_component_sync (ECalMetaBackend *meta_backend,
                                GError **error)
 {
        ECalBackendM365 *cbm365;
-       gboolean success = FALSE;
+       gboolean success;
 
        g_return_val_if_fail (E_IS_CAL_BACKEND_M365 (meta_backend), FALSE);
        g_return_val_if_fail (object != NULL, FALSE);
@@ -2712,8 +3339,19 @@ ecb_m365_remove_component_sync (ECalMetaBackend *meta_backend,
 
        LOCK (cbm365);
 
-       success = e_m365_connection_delete_event_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
-               cbm365->priv->folder_id, uid, cancellable, error);
+       switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbm365))) {
+       case I_CAL_VEVENT_COMPONENT:
+               success = e_m365_connection_delete_event_sync (cbm365->priv->cnc, NULL, 
cbm365->priv->group_id,
+                       cbm365->priv->folder_id, uid, cancellable, error);
+               break;
+       case I_CAL_VTODO_COMPONENT:
+               success = e_m365_connection_delete_task_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
+                       cbm365->priv->folder_id, uid, cancellable, error);
+               break;
+       default:
+               g_warn_if_reached ();
+               success = FALSE;
+       }
 
        UNLOCK (cbm365);
 
@@ -2738,6 +3376,11 @@ ecb_m365_discard_alarm_sync (ECalBackendSync *cal_backend_sync,
        g_return_if_fail (E_IS_CAL_BACKEND_M365 (cal_backend_sync));
        g_return_if_fail (uid != NULL);
 
+       if (e_cal_backend_get_kind (E_CAL_BACKEND (cal_backend_sync)) != I_CAL_VEVENT_COMPONENT) {
+               g_propagate_error (error, EC_ERROR (E_CLIENT_ERROR_NOT_SUPPORTED));
+               return;
+       }
+
        cbm365 = E_CAL_BACKEND_M365 (cal_backend_sync);
 
        if (!e_cal_meta_backend_ensure_connected_sync (E_CAL_META_BACKEND (cbm365), cancellable, error))
@@ -2772,6 +3415,11 @@ ecb_m365_get_free_busy_sync (ECalBackendSync *cal_backend_sync,
        g_return_if_fail (users != NULL);
        g_return_if_fail (out_freebusyobjs != NULL);
 
+       if (e_cal_backend_get_kind (E_CAL_BACKEND (cal_backend_sync)) != I_CAL_VEVENT_COMPONENT) {
+               g_propagate_error (error, EC_ERROR (E_CLIENT_ERROR_NOT_SUPPORTED));
+               return;
+       }
+
        cbm365 = E_CAL_BACKEND_M365 (cal_backend_sync);
 
        if (!e_cal_meta_backend_ensure_connected_sync (E_CAL_META_BACKEND (cbm365), cancellable, error))
diff --git a/src/Microsoft365/common/e-m365-connection.c b/src/Microsoft365/common/e-m365-connection.c
index b578640e..7c12e0e2 100644
--- a/src/Microsoft365/common/e-m365-connection.c
+++ b/src/Microsoft365/common/e-m365-connection.c
@@ -1589,6 +1589,12 @@ e_m365_connection_authenticate_sync (EM365Connection *cnc,
 
                success = e_m365_connection_get_calendar_folder_sync (cnc, user_override, group_id, 
folder_id, "name", &object, cancellable, error);
                break;
+       case E_M365_FOLDER_KIND_TASKS:
+               if (!folder_id || !*folder_id)
+                       folder_id = "tasks";
+
+               success = e_m365_connection_get_task_folder_sync (cnc, user_override, group_id, folder_id, 
"name", &object, cancellable, error);
+               break;
        }
 
        if (success) {
@@ -3858,7 +3864,7 @@ e_m365_connection_create_calendar_sync (EM365Connection *cnc,
        g_return_val_if_fail (out_created_calendar != NULL, FALSE);
 
        uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
-               group_id ? "calendarGroups" : "calendarGroup",
+               group_id ? "calendarGroups" : "calendars",
                group_id,
                "calendars",
                NULL);
@@ -4065,6 +4071,23 @@ e_m365_connection_delete_calendar_sync (EM365Connection *cnc,
        return success;
 }
 
+static void
+m365_connection_prefer_outlook_timezone (SoupMessage *message,
+                                        const gchar *prefer_outlook_timezone)
+{
+       g_return_if_fail (SOUP_IS_MESSAGE (message));
+
+       if (prefer_outlook_timezone && *prefer_outlook_timezone) {
+               gchar *prefer_value;
+
+               prefer_value = g_strdup_printf ("outlook.timezone=\"%s\"", prefer_outlook_timezone);
+
+               soup_message_headers_append (message->request_headers, "Prefer", prefer_value);
+
+               g_free (prefer_value);
+       }
+}
+
 /* https://docs.microsoft.com/en-us/graph/api/user-list-events?view=graph-rest-1.0&tabs=http */
 
 gboolean
@@ -4106,15 +4129,7 @@ e_m365_connection_list_events_sync (EM365Connection *cnc,
 
        g_free (uri);
 
-       if (prefer_outlook_timezone && *prefer_outlook_timezone) {
-               gchar *prefer_value;
-
-               prefer_value = g_strdup_printf ("outlook.timezone=\"%s\"", prefer_outlook_timezone);
-
-               soup_message_headers_append (message->request_headers, "Prefer", prefer_value);
-
-               g_free (prefer_value);
-       }
+       m365_connection_prefer_outlook_timezone (message, prefer_outlook_timezone);
 
        soup_message_headers_append (message->request_headers, "Prefer", 
"outlook.body-content-type=\"text\"");
 
@@ -4138,7 +4153,7 @@ e_m365_connection_create_event_sync (EM365Connection *cnc,
                                     const gchar *group_id, /* nullable, then the default group is used */
                                     const gchar *calendar_id,
                                     JsonBuilder *event,
-                                    EM365Calendar **out_created_event,
+                                    EM365Event **out_created_event,
                                     GCancellable *cancellable,
                                     GError **error)
 {
@@ -4217,16 +4232,7 @@ e_m365_connection_prepare_get_event (EM365Connection *cnc,
 
        g_free (uri);
 
-       if (prefer_outlook_timezone && *prefer_outlook_timezone) {
-               gchar *prefer_value;
-
-               prefer_value = g_strdup_printf ("outlook.timezone=\"%s\"", prefer_outlook_timezone);
-
-               soup_message_headers_append (message->request_headers, "Prefer", prefer_value);
-
-               g_free (prefer_value);
-       }
-
+       m365_connection_prefer_outlook_timezone (message, prefer_outlook_timezone);
        soup_message_headers_append (message->request_headers, "Prefer", 
"outlook.body-content-type=\"text\"");
 
        return message;
@@ -4866,3 +4872,1136 @@ e_m365_connection_get_schedule_sync (EM365Connection *cnc,
 
        return success;
 }
+
+/* https://docs.microsoft.com/en-us/graph/api/outlookuser-list-taskgroups?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_list_task_groups_sync (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                GSList **out_groups, /* EM365TaskGroup * - the returned 
outlookTaskGroup objects */
+                                                GCancellable *cancellable,
+                                                GError **error)
+{
+       EM365ResponseData rd;
+       SoupMessage *message;
+       gchar *uri;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (out_groups != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook", "taskGroups", NULL, NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       memset (&rd, 0, sizeof (EM365ResponseData));
+
+       rd.out_items = out_groups;
+
+       success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlookuser-post-taskgroups?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_create_task_group_sync (EM365Connection *cnc,
+                                         const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                         const gchar *name,
+                                         EM365TaskGroup **out_created_group,
+                                         GCancellable *cancellable,
+                                         GError **error)
+{
+       SoupMessage *message;
+       JsonBuilder *builder;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+       g_return_val_if_fail (out_created_group != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook", "taskGroups", NULL, NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       builder = json_builder_new_immutable ();
+
+       e_m365_json_begin_object_member (builder, NULL);
+       e_m365_json_add_string_member (builder, "name", name);
+       e_m365_json_end_object_member (builder);
+
+       e_m365_connection_set_json_body (message, builder);
+
+       g_object_unref (builder);
+
+       success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, 
out_created_group, cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktaskgroup-get?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_get_task_group_sync (EM365Connection *cnc,
+                                      const gchar *user_override, /* for which user, NULL to use the account 
user */
+                                      const gchar *group_id,
+                                      EM365TaskGroup **out_group,
+                                      GCancellable *cancellable,
+                                      GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (group_id != NULL, FALSE);
+       g_return_val_if_fail (out_group != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook",
+               "taskGroups",
+               group_id,
+               NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, 
out_group, cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktaskgroup-update?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_update_task_group_sync (EM365Connection *cnc,
+                                         const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                         const gchar *group_id,
+                                         const gchar *name,
+                                         GCancellable *cancellable,
+                                         GError **error)
+{
+       SoupMessage *message;
+       JsonBuilder *builder;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (group_id != NULL, FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook",
+               "taskGroups",
+               group_id,
+               NULL,
+               NULL);
+
+       message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       builder = json_builder_new_immutable ();
+
+       e_m365_json_begin_object_member (builder, NULL);
+       e_m365_json_add_string_member (builder, "name", name);
+       e_m365_json_end_object_member (builder);
+
+       e_m365_connection_set_json_body (message, builder);
+
+       g_object_unref (builder);
+
+       success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-delete?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_delete_task_group_sync (EM365Connection *cnc,
+                                         const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                         const gchar *group_id,
+                                         GCancellable *cancellable,
+                                         GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (group_id != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook", "taskGroups", group_id, NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlookuser-list-taskfolders?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_list_task_folders_sync (EM365Connection *cnc,
+                                         const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                         const gchar *group_id, /* nullable, task group id for group task 
folders */
+                                         const gchar *select, /* properties to select, nullable */
+                                         GSList **out_folders, /* EM365TaskFolder * - the returned 
outlookTaskFolder objects */
+                                         GCancellable *cancellable,
+                                         GError **error)
+{
+       EM365ResponseData rd;
+       SoupMessage *message;
+       gchar *uri;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (out_folders != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook",
+               group_id ? "taskGroups" : "taskFolders",
+               group_id,
+               "", group_id ? "taskFolders" : NULL,
+               "$select", select,
+               NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       memset (&rd, 0, sizeof (EM365ResponseData));
+
+       rd.out_items = out_folders;
+
+       success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlookuser-post-taskfolders?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_create_task_folder_sync (EM365Connection *cnc,
+                                          const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                          const gchar *group_id, /* nullable, then the default group is used 
*/
+                                          JsonBuilder *task_folder,
+                                          EM365TaskFolder **out_created_folder,
+                                          GCancellable *cancellable,
+                                          GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder != NULL, FALSE);
+       g_return_val_if_fail (out_created_folder != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook",
+               group_id ? "taskGroups" : "taskFolders",
+               group_id,
+               "", "taskFolders",
+               NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       e_m365_connection_set_json_body (message, task_folder);
+
+       success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, 
out_created_folder, cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-get?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_get_task_folder_sync (EM365Connection *cnc,
+                                       const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                       const gchar *group_id, /* nullable - then the default group is used */
+                                       const gchar *task_folder_id,
+                                       const gchar *select, /* nullable - properties to select */
+                                       EM365TaskFolder **out_task_folder,
+                                       GCancellable *cancellable,
+                                       GError **error)
+{
+       SoupMessage *message;
+       gchar *uri;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder_id != NULL, FALSE);
+       g_return_val_if_fail (out_task_folder != NULL, FALSE);
+
+       if (group_id) {
+               uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+                       "outlook",
+                       "taskGroups",
+                       group_id,
+                       "", "taskFolders",
+                       "", task_folder_id,
+                       "$select", select,
+                       NULL);
+       } else {
+               uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+                       "outlook",
+                       "taskFolders",
+                       task_folder_id,
+                       "$select", select,
+                       NULL);
+       }
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, 
out_task_folder, cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-update?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_update_task_folder_sync (EM365Connection *cnc,
+                                          const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                          const gchar *group_id, /* nullable - then the default group is 
used */
+                                          const gchar *task_folder_id,
+                                          const gchar *name,
+                                          GCancellable *cancellable,
+                                          GError **error)
+{
+       SoupMessage *message;
+       JsonBuilder *builder;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder_id != NULL, FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+
+       if (group_id) {
+               uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+                       "outlook",
+                       "taskGroups",
+                       group_id,
+                       "", "taskFolders",
+                       "", task_folder_id,
+                       NULL);
+       } else {
+               uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+                       "outlook",
+                       "taskFolders",
+                       task_folder_id,
+                       NULL);
+       }
+
+       message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       builder = json_builder_new_immutable ();
+
+       e_m365_json_begin_object_member (builder, NULL);
+       e_m365_json_add_string_member (builder, "name", name);
+       e_m365_json_end_object_member (builder);
+
+       e_m365_connection_set_json_body (message, builder);
+
+       g_object_unref (builder);
+
+       success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-delete?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_delete_task_folder_sync (EM365Connection *cnc,
+                                          const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                          const gchar *group_id, /* nullable - then the default group is 
used */
+                                          const gchar *task_folder_id,
+                                          GCancellable *cancellable,
+                                          GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder_id != NULL, FALSE);
+
+       if (group_id) {
+               uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+                       "outlook",
+                       "taskGroups",
+                       group_id,
+                       "", "taskFolders",
+                       "", task_folder_id,
+                       NULL);
+       } else {
+               uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+                       "outlook",
+                       "taskFolders",
+                       task_folder_id,
+                       NULL);
+       }
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-list-tasks?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_list_tasks_sync (EM365Connection *cnc,
+                                  const gchar *user_override, /* for which user, NULL to use the account 
user */
+                                  const gchar *group_id, /* nullable, task group id for group task folders */
+                                  const gchar *task_folder_id,
+                                  const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise 
that zone for the returned times */
+                                  const gchar *select, /* nullable - properties to select */
+                                  GSList **out_tasks, /* EM365Task * - the returned task objects */
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+       EM365ResponseData rd;
+       SoupMessage *message;
+       gchar *uri;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder_id != NULL, FALSE);
+       g_return_val_if_fail (out_tasks != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook",
+               group_id ? "taskGroups" : "taskFolders",
+               group_id,
+               "", group_id ? "taskFolders" : NULL,
+               "", task_folder_id,
+               "", "tasks",
+               "$select", select,
+               NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       m365_connection_prefer_outlook_timezone (message, prefer_outlook_timezone);
+       soup_message_headers_append (message->request_headers, "Prefer", 
"outlook.body-content-type=\"text\"");
+
+       memset (&rd, 0, sizeof (EM365ResponseData));
+
+       rd.out_items = out_tasks;
+
+       success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlookuser-post-tasks?view=graph-rest-beta&tabs=csharp */
+
+gboolean
+e_m365_connection_create_task_sync (EM365Connection *cnc,
+                                   const gchar *user_override, /* for which user, NULL to use the account 
user */
+                                   const gchar *group_id, /* nullable, then the default group is used */
+                                   const gchar *task_folder_id,
+                                   JsonBuilder *task,
+                                   EM365Task **out_created_task,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder_id != NULL, FALSE);
+       g_return_val_if_fail (task != NULL, FALSE);
+       g_return_val_if_fail (out_created_task != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook",
+               group_id ? "taskGroups" : "taskFolders",
+               group_id,
+               "", group_id ? "taskFolders" : NULL,
+               "", task_folder_id,
+               "", "tasks",
+               NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       e_m365_connection_set_json_body (message, task);
+
+       success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, 
out_created_task, cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktask-get?view=graph-rest-beta&tabs=http */
+
+SoupMessage *
+e_m365_connection_prepare_get_task (EM365Connection *cnc,
+                                   const gchar *user_override, /* for which user, NULL to use the account 
user */
+                                   const gchar *group_id, /* nullable, then the default group is used */
+                                   const gchar *task_folder_id,
+                                   const gchar *task_id,
+                                   const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise 
that zone for the returned times */
+                                   const gchar *select, /* nullable - properties to select */
+                                   GError **error)
+{
+       SoupMessage *message;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
+       g_return_val_if_fail (task_folder_id != NULL, NULL);
+       g_return_val_if_fail (task_id != NULL, NULL);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook",
+               group_id ? "taskGroups" : "taskFolders",
+               group_id,
+               "", group_id ? "taskFolders" : NULL,
+               "", task_folder_id,
+               "", "tasks",
+               "", task_id,
+               "$select", select,
+               NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return NULL;
+       }
+
+       g_free (uri);
+
+       m365_connection_prefer_outlook_timezone (message, prefer_outlook_timezone);
+       soup_message_headers_append (message->request_headers, "Prefer", 
"outlook.body-content-type=\"text\"");
+
+       return message;
+}
+
+gboolean
+e_m365_connection_get_task_sync (EM365Connection *cnc,
+                                const gchar *user_override, /* for which user, NULL to use the account user 
*/
+                                const gchar *group_id, /* nullable, then the default group is used */
+                                const gchar *task_folder_id,
+                                const gchar *task_id,
+                                const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise that 
zone for the returned times */
+                                const gchar *select, /* nullable - properties to select */
+                                EM365Task **out_task,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder_id != NULL, FALSE);
+       g_return_val_if_fail (task_id != NULL, FALSE);
+       g_return_val_if_fail (out_task != NULL, FALSE);
+
+       message = e_m365_connection_prepare_get_task (cnc, user_override, group_id, task_folder_id, task_id, 
prefer_outlook_timezone, select, error);
+
+       if (!message)
+               return FALSE;
+
+       success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL, 
out_task, cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+gboolean
+e_m365_connection_get_tasks_sync (EM365Connection *cnc,
+                                 const gchar *user_override, /* for which user, NULL to use the account user 
*/
+                                 const gchar *group_id, /* nullable, then the default group is used */
+                                 const gchar *task_folder_id,
+                                 const GSList *task_ids, /* const gchar * */
+                                 const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise 
that zone for the returned times */
+                                 const gchar *select, /* nullable - properties to select */
+                                 GSList **out_tasks, /* EM365Task *, in the same order as task_ids; can 
return partial list */
+                                 GCancellable *cancellable,
+                                 GError **error)
+{
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder_id != NULL, FALSE);
+       g_return_val_if_fail (task_ids != NULL, FALSE);
+       g_return_val_if_fail (out_tasks != NULL, FALSE);
+
+       if (g_slist_next (task_ids)) {
+               GPtrArray *requests;
+               GSList *link;
+               guint total, done = 0;
+
+               total = g_slist_length ((GSList *) task_ids);
+               requests = g_ptr_array_new_full (MIN (E_M365_BATCH_MAX_REQUESTS, MIN (total, 50)), 
g_object_unref);
+
+               for (link = (GSList *) task_ids; link && success; link = g_slist_next (link)) {
+                       const gchar *id = link->data;
+                       SoupMessage *message;
+
+                       message = e_m365_connection_prepare_get_task (cnc, user_override, group_id, 
task_folder_id, id, prefer_outlook_timezone, select, error);
+
+                       if (!message) {
+                               success = FALSE;
+                               break;
+                       }
+
+                       g_ptr_array_add (requests, message);
+
+                       if (requests->len == E_M365_BATCH_MAX_REQUESTS || !link->next) {
+                               if (requests->len == 1) {
+                                       EM365Task *task = NULL;
+
+                                       success = m365_connection_send_request_sync (cnc, message, 
e_m365_read_json_object_response_cb, NULL, &task, cancellable, error);
+
+                                       if (success)
+                                               *out_tasks = g_slist_prepend (*out_tasks, task);
+                               } else {
+                                       success = e_m365_connection_batch_request_sync (cnc, E_M365_API_BETA, 
requests, cancellable, error);
+
+                                       if (success) {
+                                               guint ii;
+
+                                               for (ii = 0; ii < requests->len && success; ii++) {
+                                                       JsonNode *node = NULL;
+
+                                                       message = requests->pdata[ii];
+                                                       success = e_m365_connection_json_node_from_message 
(message, NULL, &node, cancellable, error);
+
+                                                       if (success && node && JSON_NODE_HOLDS_OBJECT (node)) 
{
+                                                               JsonObject *response;
+
+                                                               response = json_node_get_object (node);
+
+                                                               if (response) {
+                                                                       *out_tasks = g_slist_prepend 
(*out_tasks, json_object_ref (response));
+                                                               } else {
+                                                                       success = FALSE;
+                                                               }
+                                                       } else {
+                                                               success = FALSE;
+                                                       }
+
+                                                       if (node)
+                                                               json_node_unref (node);
+                                               }
+                                       }
+                               }
+
+                               g_ptr_array_remove_range (requests, 0, requests->len);
+
+                               done += requests->len;
+
+                               camel_operation_progress (cancellable, done * 100.0 / total);
+                       }
+               }
+
+               g_ptr_array_free (requests, TRUE);
+       } else {
+               SoupMessage *message;
+
+               message = e_m365_connection_prepare_get_task (cnc, user_override, group_id, task_folder_id, 
task_ids->data, prefer_outlook_timezone, select, error);
+
+               if (message) {
+                       EM365Task *task = NULL;
+
+                       success = m365_connection_send_request_sync (cnc, message, 
e_m365_read_json_object_response_cb, NULL, &task, cancellable, error);
+
+                       if (success)
+                               *out_tasks = g_slist_prepend (*out_tasks, task);
+
+                       g_clear_object (&message);
+               } else {
+                       success = FALSE;
+               }
+       }
+
+       *out_tasks = g_slist_reverse (*out_tasks);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktask-update?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_update_task_sync (EM365Connection *cnc,
+                                   const gchar *user_override, /* for which user, NULL to use the account 
user */
+                                   const gchar *group_id, /* nullable - then the default group is used */
+                                   const gchar *task_folder_id,
+                                   const gchar *task_id,
+                                   JsonBuilder *task,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder_id != NULL, FALSE);
+       g_return_val_if_fail (task_id != NULL, FALSE);
+       g_return_val_if_fail (task != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook",
+               group_id ? "taskGroups" : "taskFolders",
+               group_id,
+               "", group_id ? "taskFolders" : NULL,
+               "", task_folder_id,
+               "", "tasks",
+               "", task_id,
+               NULL);
+
+       message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       e_m365_connection_set_json_body (message, task);
+
+       success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktask-delete?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_delete_task_sync (EM365Connection *cnc,
+                                   const gchar *user_override, /* for which user, NULL to use the account 
user */
+                                   const gchar *group_id, /* nullable - then the default group is used */
+                                   const gchar *task_folder_id,
+                                   const gchar *task_id,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder_id != NULL, FALSE);
+       g_return_val_if_fail (task_id != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook",
+               group_id ? "taskGroups" : "taskFolders",
+               group_id,
+               "", group_id ? "taskFolders" : NULL,
+               "", task_folder_id,
+               "", "tasks",
+               "", task_id,
+               NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktask-complete?view=graph-rest-beta */
+
+gboolean
+e_m365_connection_complete_task_sync (EM365Connection *cnc,
+                                     const gchar *user_override, /* for which user, NULL to use the account 
user */
+                                     const gchar *group_id, /* nullable - then the default group is used */
+                                     const gchar *task_folder_id,
+                                     const gchar *task_id,
+                                     GCancellable *cancellable,
+                                     GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder_id != NULL, FALSE);
+       g_return_val_if_fail (task_id != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook",
+               group_id ? "taskGroups" : "taskFolders",
+               group_id,
+               "", group_id ? "taskFolders" : NULL,
+               "", task_folder_id,
+               "", "tasks",
+               "", task_id,
+               "", "complete",
+               NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DISABLE_RESPONSE, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktask-list-attachments?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_list_task_attachments_sync (EM365Connection *cnc,
+                                             const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                             const gchar *group_id, /* nullable, then the default group is 
used */
+                                             const gchar *task_folder_id,
+                                             const gchar *task_id,
+                                             const gchar *select, /* nullable - properties to select */
+                                             GSList **out_attachments, /* EM365Attachment * */
+                                             GCancellable *cancellable,
+                                             GError **error)
+{
+       EM365ResponseData rd;
+       SoupMessage *message;
+       gchar *uri;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder_id != NULL, FALSE);
+       g_return_val_if_fail (task_id != NULL, FALSE);
+       g_return_val_if_fail (out_attachments != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook",
+               group_id ? "taskGroups" : "taskFolders",
+               group_id,
+               "", group_id ? "taskFolders" : NULL,
+               "", task_folder_id,
+               "", "tasks",
+               "", task_id,
+               "", "attachments",
+               "$select", select,
+               NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       memset (&rd, 0, sizeof (EM365ResponseData));
+
+       rd.out_items = out_attachments;
+
+       success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/attachment-get?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_get_task_attachment_sync (EM365Connection *cnc,
+                                           const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                           const gchar *group_id, /* nullable, then the default group is 
used */
+                                           const gchar *task_folder_id,
+                                           const gchar *task_id,
+                                           const gchar *attachment_id,
+                                           EM365ConnectionRawDataFunc func,
+                                           gpointer func_user_data,
+                                           GCancellable *cancellable,
+                                           GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder_id != NULL, FALSE);
+       g_return_val_if_fail (task_id != NULL, FALSE);
+       g_return_val_if_fail (attachment_id != NULL, FALSE);
+       g_return_val_if_fail (func != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook",
+               group_id ? "taskGroups" : "taskFolders",
+               group_id,
+               "", group_id ? "taskFolders" : NULL,
+               "", task_folder_id,
+               "", "tasks",
+               "", task_id,
+               "", "attachments",
+               "", attachment_id,
+               "", "$value",
+               NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       success = m365_connection_send_request_sync (cnc, message, NULL, func, func_user_data, cancellable, 
error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktask-post-attachments?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_add_task_attachment_sync (EM365Connection *cnc,
+                                           const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                           const gchar *group_id, /* nullable, then the default group is 
used */
+                                           const gchar *task_folder_id,
+                                           const gchar *task_id,
+                                           JsonBuilder *in_attachment,
+                                           EM365Attachment **out_attachment, /* nullable */
+                                           GCancellable *cancellable,
+                                           GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder_id != NULL, FALSE);
+       g_return_val_if_fail (task_id != NULL, FALSE);
+       g_return_val_if_fail (in_attachment != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook",
+               group_id ? "taskGroups" : "taskFolders",
+               group_id,
+               "", group_id ? "taskFolders" : NULL,
+               "", task_folder_id,
+               "", "tasks",
+               "", task_id,
+               "", "attachments",
+               NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, out_attachment ? CSM_DEFAULT : 
CSM_DISABLE_RESPONSE, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       e_m365_connection_set_json_body (message, in_attachment);
+
+       success = m365_connection_send_request_sync (cnc, message, out_attachment ? 
e_m365_read_json_object_response_cb : NULL,
+               out_attachment ? NULL : e_m365_read_no_response_cb, out_attachment, cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/attachment-delete?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_delete_task_attachment_sync (EM365Connection *cnc,
+                                              const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                              const gchar *group_id, /* nullable, then the default group is 
used */
+                                              const gchar *task_folder_id,
+                                              const gchar *task_id,
+                                              const gchar *attachment_id,
+                                              GCancellable *cancellable,
+                                              GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (task_folder_id != NULL, FALSE);
+       g_return_val_if_fail (task_id != NULL, FALSE);
+       g_return_val_if_fail (attachment_id != NULL, FALSE);
+
+       uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+               "outlook",
+               group_id ? "taskGroups" : "taskFolders",
+               group_id,
+               "", group_id ? "taskFolders" : NULL,
+               "", task_folder_id,
+               "", "tasks",
+               "", task_id,
+               "", "attachments",
+               "", attachment_id,
+               NULL);
+
+       message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
diff --git a/src/Microsoft365/common/e-m365-connection.h b/src/Microsoft365/common/e-m365-connection.h
index df6deff9..e12a18be 100644
--- a/src/Microsoft365/common/e-m365-connection.h
+++ b/src/Microsoft365/common/e-m365-connection.h
@@ -446,7 +446,7 @@ gboolean    e_m365_connection_create_event_sync
                                                 const gchar *group_id, /* nullable, then the default group 
is used */
                                                 const gchar *calendar_id,
                                                 JsonBuilder *event,
-                                                EM365Calendar **out_created_event,
+                                                EM365Event **out_created_event,
                                                 GCancellable *cancellable,
                                                 GError **error);
 SoupMessage *  e_m365_connection_prepare_get_event
@@ -566,6 +566,192 @@ gboolean  e_m365_connection_get_schedule_sync
                                                 GSList **out_infos, /* EM365ScheduleInformation * */
                                                 GCancellable *cancellable,
                                                 GError **error);
+gboolean       e_m365_connection_list_task_groups_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                GSList **out_groups, /* EM365TaskGroup * - the returned 
outlookTaskGroup objects */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_create_task_group_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *name,
+                                                EM365TaskGroup **out_created_group,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_get_task_group_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id,
+                                                EM365TaskGroup **out_group,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_update_task_group_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id,
+                                                const gchar *name,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_delete_task_group_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_list_task_folders_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable, task group id for group 
task folders */
+                                                const gchar *select, /* properties to select, nullable */
+                                                GSList **out_folders, /* EM365TaskFolder * - the returned 
outlookTaskFolder objects */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_get_task_folder_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable - then the default group 
is used */
+                                                const gchar *task_folder_id,
+                                                const gchar *select, /* nullable - properties to select */
+                                                EM365TaskFolder **out_task_folder,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_create_task_folder_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable, then the default group 
is used */
+                                                JsonBuilder *task_folder,
+                                                EM365TaskFolder **out_created_folder,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_update_task_folder_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable - then the default group 
is used */
+                                                const gchar *task_folder_id,
+                                                const gchar *name,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_delete_task_folder_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable - then the default group 
is used */
+                                                const gchar *task_folder_id,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_list_tasks_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable, task group id for group 
task folders */
+                                                const gchar *task_folder_id,
+                                                const gchar *prefer_outlook_timezone, /* nullable - then 
UTC, otherwise that zone for the returned times */
+                                                const gchar *select, /* nullable - properties to select */
+                                                GSList **out_tasks, /* EM365Task * - the returned task 
objects */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_create_task_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable, then the default group 
is used */
+                                                const gchar *task_folder_id,
+                                                JsonBuilder *task,
+                                                EM365Task **out_created_task,
+                                                GCancellable *cancellable,
+                                                GError **error);
+SoupMessage *  e_m365_connection_prepare_get_task
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable, then the default group 
is used */
+                                                const gchar *task_folder_id,
+                                                const gchar *task_id,
+                                                const gchar *prefer_outlook_timezone, /* nullable - then 
UTC, otherwise that zone for the returned times */
+                                                const gchar *select, /* nullable - properties to select */
+                                                GError **error);
+gboolean       e_m365_connection_get_task_sync (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable, then the default group 
is used */
+                                                const gchar *task_folder_id,
+                                                const gchar *task_id,
+                                                const gchar *prefer_outlook_timezone, /* nullable - then 
UTC, otherwise that zone for the returned times */
+                                                const gchar *select, /* nullable - properties to select */
+                                                EM365Task **out_task,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_get_tasks_sync(EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable, then the default group 
is used */
+                                                const gchar *task_folder_id,
+                                                const GSList *task_ids, /* const gchar * */
+                                                const gchar *prefer_outlook_timezone, /* nullable - then 
UTC, otherwise that zone for the returned times */
+                                                const gchar *select, /* nullable - properties to select */
+                                                GSList **out_tasks, /* EM365Task *, in the same order as 
task_ids; can return partial list */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_update_task_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable - then the default group 
is used */
+                                                const gchar *task_folder_id,
+                                                const gchar *task_id,
+                                                JsonBuilder *task,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_delete_task_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable - then the default group 
is used */
+                                                const gchar *task_folder_id,
+                                                const gchar *task_id,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_complete_task_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable - then the default group 
is used */
+                                                const gchar *task_folder_id,
+                                                const gchar *task_id,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_list_task_attachments_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable, then the default group 
is used */
+                                                const gchar *task_folder_id,
+                                                const gchar *task_id,
+                                                const gchar *select, /* nullable - properties to select */
+                                                GSList **out_attachments, /* EM365Attachment * */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_get_task_attachment_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable, then the default group 
is used */
+                                                const gchar *task_folder_id,
+                                                const gchar *task_id,
+                                                const gchar *attachment_id,
+                                                EM365ConnectionRawDataFunc func,
+                                                gpointer func_user_data,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_add_task_attachment_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable, then the default group 
is used */
+                                                const gchar *task_folder_id,
+                                                const gchar *task_id,
+                                                JsonBuilder *in_attachment,
+                                                EM365Attachment **out_attachment, /* nullable */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_m365_connection_delete_task_attachment_sync
+                                               (EM365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable, then the default group 
is used */
+                                                const gchar *task_folder_id,
+                                                const gchar *task_id,
+                                                const gchar *attachment_id,
+                                                GCancellable *cancellable,
+                                                GError **error);
 
 G_END_DECLS
 
diff --git a/src/Microsoft365/common/e-m365-json-utils.c b/src/Microsoft365/common/e-m365-json-utils.c
index 84fa63a0..9427331d 100644
--- a/src/Microsoft365/common/e-m365-json-utils.c
+++ b/src/Microsoft365/common/e-m365-json-utils.c
@@ -235,7 +235,14 @@ static MapData sensitivity_map[] = {
        { "personal",           E_M365_SENSITIVITY_PERSONAL },
        { "private",            E_M365_SENSITIVITY_PRIVATE },
        { "confidential",       E_M365_SENSITIVITY_CONFIDENTIAL }
+};
 
+static MapData status_map[] = {
+       { "notStarted",         E_M365_STATUS_NOT_STARTED },
+       { "inProgress",         E_M365_STATUS_IN_PROGRESS },
+       { "completed",          E_M365_STATUS_COMPLETED },
+       { "waitingOnOthers",    E_M365_STATUS_WAITING_ON_OTHERS },
+       { "deferred",           E_M365_STATUS_DEFERRED }
 };
 
 static MapData week_index_map[] = {
@@ -3555,3 +3562,328 @@ e_m365_schedule_information_get_working_hours (EM365ScheduleInformation *schinfo
 {
        return e_m365_json_get_object_member (schinfo, "workingHours");
 }
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/outlooktaskfolder?view=graph-rest-beta */
+
+const gchar *
+e_m365_task_folder_get_id (EM365TaskFolder *folder)
+{
+       return e_m365_json_get_string_member (folder, "id", NULL);
+}
+
+const gchar *
+e_m365_task_folder_get_change_key (EM365TaskFolder *folder)
+{
+       return e_m365_json_get_string_member (folder, "changeKey", NULL);
+}
+
+const gchar *
+e_m365_task_folder_get_parent_group_key (EM365TaskFolder *folder)
+{
+       return e_m365_json_get_string_member (folder, "parentGroupKey", NULL);
+}
+
+const gchar *
+e_m365_task_folder_get_name (EM365TaskFolder *folder)
+{
+       return e_m365_json_get_string_member (folder, "name", NULL);
+}
+
+gboolean
+e_m365_task_folder_get_is_default_folder (EM365TaskFolder *folder)
+{
+       return e_m365_json_get_boolean_member (folder, "isDefaultFolder", FALSE);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/outlooktaskgroup?view=graph-rest-beta */
+
+const gchar *
+e_m365_task_group_get_id (EM365TaskGroup *group)
+{
+       return e_m365_json_get_string_member (group, "id", NULL);
+}
+
+const gchar *
+e_m365_task_group_get_change_key (EM365TaskGroup *group)
+{
+       return e_m365_json_get_string_member (group, "changeKey", NULL);
+}
+
+const gchar *
+e_m365_task_group_get_group_key (EM365TaskGroup *group)
+{
+       return e_m365_json_get_string_member (group, "groupKey", NULL);
+}
+
+const gchar *
+e_m365_task_group_get_name (EM365TaskGroup *group)
+{
+       return e_m365_json_get_string_member (group, "name", NULL);
+}
+
+gboolean
+e_m365_task_group_get_is_default_group (EM365TaskGroup *group)
+{
+       return e_m365_json_get_boolean_member (group, "isDefaultGroup", FALSE);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/outlooktask?view=graph-rest-beta */
+
+const gchar *
+e_m365_task_get_id (EM365Task *task)
+{
+       return e_m365_json_get_string_member (task, "id", NULL);
+}
+
+const gchar *
+e_m365_task_get_change_key (EM365Task *task)
+{
+       return e_m365_json_get_string_member (task, "changeKey", NULL);
+}
+
+const gchar *
+e_m365_task_get_parent_folder_id (EM365Task *task)
+{
+       return e_m365_json_get_string_member (task, "parentFolderId", NULL);
+}
+
+const gchar *
+e_m365_task_get_assigned_to (EM365Task *task)
+{
+       return e_m365_json_get_string_member (task, "assignedTo", NULL);
+}
+
+EM365ItemBody *
+e_m365_task_get_body (EM365Task *task)
+{
+       return e_m365_json_get_object_member (task, "body");
+}
+
+void
+e_m365_task_add_body (JsonBuilder *builder,
+                     EM365ItemBodyContentTypeType content_type,
+                     const gchar *content)
+{
+       e_m365_add_item_body (builder, "body", content_type, content);
+}
+
+JsonArray * /* const gchar * */
+e_m365_task_get_categories (EM365Task *task)
+{
+       return e_m365_json_get_array_member (task, "categories");
+}
+
+void
+e_m365_task_begin_categories (JsonBuilder *builder)
+{
+       e_m365_json_begin_array_member (builder, "categories");
+}
+
+void
+e_m365_task_end_categories (JsonBuilder *builder)
+{
+       e_m365_json_end_array_member (builder);
+}
+
+void
+e_m365_task_add_category (JsonBuilder *builder,
+                         const gchar *category)
+{
+       g_return_if_fail (category && *category);
+
+       json_builder_add_string_value (builder, category);
+}
+
+EM365DateTimeWithZone *
+e_m365_task_get_completed_date_time (EM365Task *task)
+{
+       return e_m365_json_get_object_member (task, "completedDateTime");
+}
+
+void
+e_m365_task_add_completed_date_time (JsonBuilder *builder,
+                                    time_t date_time,
+                                    const gchar *zone)
+{
+       e_m365_add_date_time (builder, "completedDateTime", date_time, zone);
+}
+
+time_t
+e_m365_task_get_created_date_time (EM365Task *task)
+{
+       return e_m365_get_date_time_offset_member (task, "createdDateTime");
+}
+
+EM365DateTimeWithZone *
+e_m365_task_get_due_date_time (EM365Task *task)
+{
+       return e_m365_json_get_object_member (task, "dueDateTime");
+}
+
+void
+e_m365_task_add_due_date_time (JsonBuilder *builder,
+                              time_t date_time,
+                              const gchar *zone)
+{
+       e_m365_add_date_time (builder, "dueDateTime", date_time, zone);
+}
+
+gboolean
+e_m365_task_get_has_attachments (EM365Task *task)
+{
+       return e_m365_json_get_boolean_member (task, "hasAttachments", FALSE);
+}
+
+EM365ImportanceType
+e_m365_task_get_importance (EM365Task *task)
+{
+       return m365_json_utils_get_json_as_enum (task, "importance",
+               importance_map, G_N_ELEMENTS (importance_map),
+               E_M365_IMPORTANCE_NOT_SET,
+               E_M365_IMPORTANCE_UNKNOWN);
+}
+
+void
+e_m365_task_add_importance (JsonBuilder *builder,
+                           EM365ImportanceType value)
+{
+       m365_json_utils_add_enum_as_json (builder, "importance", value,
+               importance_map, G_N_ELEMENTS (importance_map),
+               E_M365_IMPORTANCE_NOT_SET,
+               E_M365_IMPORTANCE_NOT_SET);
+}
+
+gboolean
+e_m365_task_get_is_reminder_on (EM365Task *task)
+{
+       return e_m365_json_get_boolean_member (task, "isReminderOn", FALSE);
+}
+
+void
+e_m365_task_add_is_reminder_on (JsonBuilder *builder,
+                               gboolean value)
+{
+       e_m365_json_add_boolean_member (builder, "isReminderOn", value);
+}
+
+time_t
+e_m365_task_get_last_modified_date_time (EM365Task *task)
+{
+       return e_m365_get_date_time_offset_member (task, "lastModifiedDateTime");
+}
+
+const gchar *
+e_m365_task_get_owner (EM365Task *task)
+{
+       return e_m365_json_get_string_member (task, "owner", NULL);
+}
+
+void
+e_m365_task_add_owner (JsonBuilder *builder,
+                      const gchar *value)
+{
+       e_m365_json_add_string_member (builder, "owner", value);
+}
+
+EM365PatternedRecurrence *
+e_m365_task_get_recurrence (EM365Task *task)
+{
+       return e_m365_json_get_object_member (task, "recurrence");
+}
+
+void
+e_m365_task_begin_recurrence (JsonBuilder *builder)
+{
+       e_m365_json_begin_object_member (builder, "recurrence");
+}
+
+void
+e_m365_task_end_recurrence (JsonBuilder *builder)
+{
+       e_m365_json_end_object_member (builder);
+}
+
+void
+e_m365_task_add_null_recurrence (JsonBuilder *builder)
+{
+       e_m365_json_add_null_member (builder, "recurrence");
+}
+
+EM365DateTimeWithZone *
+e_m365_task_get_reminder_date_time (EM365Task *task)
+{
+       return e_m365_json_get_object_member (task, "reminderDateTime");
+}
+
+void
+e_m365_task_add_reminder_date_time (JsonBuilder *builder,
+                                   time_t date_time,
+                                   const gchar *zone)
+{
+       e_m365_add_date_time (builder, "reminderDateTime", date_time, zone);
+}
+
+EM365SensitivityType
+e_m365_task_get_sensitivity (EM365Task *task)
+{
+       return m365_json_utils_get_json_as_enum (task, "sensitivity",
+               sensitivity_map, G_N_ELEMENTS (sensitivity_map),
+               E_M365_SENSITIVITY_NOT_SET,
+               E_M365_SENSITIVITY_UNKNOWN);
+}
+
+void
+e_m365_task_add_sensitivity (JsonBuilder *builder,
+                            EM365SensitivityType value)
+{
+       m365_json_utils_add_enum_as_json (builder, "sensitivity", value,
+               sensitivity_map, G_N_ELEMENTS (sensitivity_map),
+               E_M365_SENSITIVITY_NOT_SET,
+               E_M365_SENSITIVITY_UNKNOWN);
+}
+
+EM365DateTimeWithZone *
+e_m365_task_get_start_date_time (EM365Task *task)
+{
+       return e_m365_json_get_object_member (task, "startDateTime");
+}
+
+void
+e_m365_task_add_start_date_time (JsonBuilder *builder,
+                                time_t date_time,
+                                const gchar *zone)
+{
+       e_m365_add_date_time (builder, "startDateTime", date_time, zone);
+}
+
+EM365StatusType
+e_m365_task_get_status (EM365Task *task)
+{
+       return m365_json_utils_get_json_as_enum (task, "status",
+               status_map, G_N_ELEMENTS (status_map),
+               E_M365_STATUS_NOT_SET,
+               E_M365_STATUS_UNKNOWN);
+}
+
+void
+e_m365_task_add_status (JsonBuilder *builder,
+                       EM365StatusType value)
+{
+       m365_json_utils_add_enum_as_json (builder, "status", value,
+               status_map, G_N_ELEMENTS (status_map),
+               E_M365_STATUS_NOT_SET,
+               E_M365_STATUS_UNKNOWN);
+}
+
+const gchar *
+e_m365_task_get_subject (EM365Task *task)
+{
+       return e_m365_json_get_string_member (task, "subject", NULL);
+}
+
+void
+e_m365_task_add_subject (JsonBuilder *builder,
+                        const gchar *value)
+{
+       e_m365_json_add_string_member (builder, "subject", value);
+}
diff --git a/src/Microsoft365/common/e-m365-json-utils.h b/src/Microsoft365/common/e-m365-json-utils.h
index 46eac480..df5a0548 100644
--- a/src/Microsoft365/common/e-m365-json-utils.h
+++ b/src/Microsoft365/common/e-m365-json-utils.h
@@ -42,6 +42,9 @@ G_BEGIN_DECLS
 #define EM365ResponseStatus            JsonObject
 #define EM365ScheduleInformation       JsonObject
 #define EM365ScheduleItem              JsonObject
+#define EM365Task                      JsonObject
+#define EM365TaskFolder                        JsonObject
+#define EM365TaskGroup                 JsonObject
 #define EM365TimeOfDay                 gint64
 #define EM365WorkingHours              JsonObject
 
@@ -215,6 +218,16 @@ typedef enum _EM365SensitivityType {
        E_M365_SENSITIVITY_CONFIDENTIAL
 } EM365SensitivityType;
 
+typedef enum _EM365StatusType {
+       E_M365_STATUS_NOT_SET,
+       E_M365_STATUS_UNKNOWN,
+       E_M365_STATUS_NOT_STARTED,
+       E_M365_STATUS_IN_PROGRESS,
+       E_M365_STATUS_COMPLETED,
+       E_M365_STATUS_WAITING_ON_OTHERS,
+       E_M365_STATUS_DEFERRED
+} EM365StatusType;
+
 typedef enum _EM365WeekIndexType {
        E_M365_WEEK_INDEX_NOT_SET,
        E_M365_WEEK_INDEX_UNKNOWN,
@@ -964,6 +977,80 @@ EM365WorkingHours *
                e_m365_schedule_information_get_working_hours
                                                        (EM365ScheduleInformation *schinfo);
 
+const gchar *  e_m365_task_folder_get_id               (EM365TaskFolder *folder);
+const gchar *  e_m365_task_folder_get_change_key       (EM365TaskFolder *folder);
+const gchar *  e_m365_task_folder_get_parent_group_key (EM365TaskFolder *folder);
+const gchar *  e_m365_task_folder_get_name             (EM365TaskFolder *folder);
+gboolean       e_m365_task_folder_get_is_default_folder(EM365TaskFolder *folder);
+
+const gchar *  e_m365_task_group_get_id                (EM365TaskGroup *group);
+const gchar *  e_m365_task_group_get_change_key        (EM365TaskGroup *group);
+const gchar *  e_m365_task_group_get_group_key         (EM365TaskGroup *group);
+const gchar *  e_m365_task_group_get_name              (EM365TaskGroup *group);
+gboolean       e_m365_task_group_get_is_default_group  (EM365TaskGroup *group);
+
+const gchar *  e_m365_task_get_id                      (EM365Task *task);
+const gchar *  e_m365_task_get_change_key              (EM365Task *task);
+const gchar *  e_m365_task_get_parent_folder_id        (EM365Task *task);
+const gchar *  e_m365_task_get_assigned_to             (EM365Task *task);
+EM365ItemBody *        e_m365_task_get_body                    (EM365Task *task);
+void           e_m365_task_add_body                    (JsonBuilder *builder,
+                                                        EM365ItemBodyContentTypeType content_type,
+                                                        const gchar *content);
+JsonArray *    e_m365_task_get_categories              (EM365Task *task); /* const gchar * */
+void           e_m365_task_begin_categories            (JsonBuilder *builder);
+void           e_m365_task_end_categories              (JsonBuilder *builder);
+void           e_m365_task_add_category                (JsonBuilder *builder,
+                                                        const gchar *category);
+EM365DateTimeWithZone *
+               e_m365_task_get_completed_date_time     (EM365Task *task);
+void           e_m365_task_add_completed_date_time     (JsonBuilder *builder,
+                                                        time_t date_time,
+                                                        const gchar *zone);
+time_t         e_m365_task_get_created_date_time       (EM365Task *task);
+EM365DateTimeWithZone *
+               e_m365_task_get_due_date_time           (EM365Task *task);
+void           e_m365_task_add_due_date_time           (JsonBuilder *builder,
+                                                        time_t date_time,
+                                                        const gchar *zone);
+gboolean       e_m365_task_get_has_attachments         (EM365Task *task);
+EM365ImportanceType
+               e_m365_task_get_importance              (EM365Task *task);
+void           e_m365_task_add_importance              (JsonBuilder *builder,
+                                                        EM365ImportanceType value);
+gboolean       e_m365_task_get_is_reminder_on          (EM365Task *task);
+void           e_m365_task_add_is_reminder_on          (JsonBuilder *builder,
+                                                        gboolean value);
+time_t         e_m365_task_get_last_modified_date_time (EM365Task *task);
+const gchar *  e_m365_task_get_owner                   (EM365Task *task);
+void           e_m365_task_add_owner                   (JsonBuilder *builder,
+                                                        const gchar *value);
+EM365PatternedRecurrence *
+               e_m365_task_get_recurrence              (EM365Task *task);
+void           e_m365_task_begin_recurrence            (JsonBuilder *builder);
+void           e_m365_task_end_recurrence              (JsonBuilder *builder);
+void           e_m365_task_add_null_recurrence         (JsonBuilder *builder);
+EM365DateTimeWithZone *
+               e_m365_task_get_reminder_date_time      (EM365Task *task);
+void           e_m365_task_add_reminder_date_time      (JsonBuilder *builder,
+                                                        time_t date_time,
+                                                        const gchar *zone);
+EM365SensitivityType
+               e_m365_task_get_sensitivity             (EM365Task *task);
+void           e_m365_task_add_sensitivity             (JsonBuilder *builder,
+                                                        EM365SensitivityType value);
+EM365DateTimeWithZone *
+               e_m365_task_get_start_date_time         (EM365Task *task);
+void           e_m365_task_add_start_date_time         (JsonBuilder *builder,
+                                                        time_t date_time,
+                                                        const gchar *zone);
+EM365StatusType        e_m365_task_get_status                  (EM365Task *task);
+void           e_m365_task_add_status                  (JsonBuilder *builder,
+                                                        EM365StatusType value);
+const gchar *  e_m365_task_get_subject                 (EM365Task *task);
+void           e_m365_task_add_subject                 (JsonBuilder *builder,
+                                                        const gchar *value);
+
 G_END_DECLS
 
 #endif /* E_M365_JSON_UTILS_H */
diff --git a/src/Microsoft365/common/e-source-m365-folder.c b/src/Microsoft365/common/e-source-m365-folder.c
index d4040a90..f333d1a9 100644
--- a/src/Microsoft365/common/e-source-m365-folder.c
+++ b/src/Microsoft365/common/e-source-m365-folder.c
@@ -204,7 +204,7 @@ e_source_m365_folder_set_id (ESourceM365Folder *extension,
        }
 
        g_free (extension->priv->id);
-       extension->priv->id = g_strdup (id);
+       extension->priv->id = e_util_strdup_strip (id);
 
        e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
 
@@ -279,7 +279,7 @@ e_source_m365_folder_set_group_id (ESourceM365Folder *extension,
        }
 
        g_free (extension->priv->group_id);
-       extension->priv->group_id = g_strdup (group_id);
+       extension->priv->group_id = e_util_strdup_strip (group_id);
 
        e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
 
diff --git a/src/Microsoft365/registry/e-m365-backend.c b/src/Microsoft365/registry/e-m365-backend.c
index d3e080f6..9468d083 100644
--- a/src/Microsoft365/registry/e-m365-backend.c
+++ b/src/Microsoft365/registry/e-m365-backend.c
@@ -408,6 +408,71 @@ m365_backend_sync_calendar_folders_sync (EM365Backend *m365_backend,
 
                                        g_hash_table_remove (known_ids, e_m365_calendar_get_id (calendar));
                                }
+
+                               g_slist_free_full (calendars, (GDestroyNotify) json_object_unref);
+                       } else {
+                               success = FALSE;
+                       }
+               }
+
+               g_slist_free_full (groups, (GDestroyNotify) json_object_unref);
+       }
+
+       if (success)
+               m365_backend_forget_folders_hash (m365_backend, extension_name, known_ids);
+
+       g_hash_table_destroy (known_ids);
+       g_clear_error (&error);
+}
+
+/* Tasks are in the beta stage (as of 2020-07-31) and even one can get groups of tasks, getting
+   information about a task folder in the group fails with an error NavigationNotSupported:
+   "Recursive navigation is not allowed after property 'TaskFolders' according to the entity schema."
+   The server returns similar error when trying to get a single task, which makes it unusable. */
+#if 0
+static void
+m365_backend_sync_task_folders_sync (EM365Backend *m365_backend,
+                                    EM365Connection *cnc,
+                                    GCancellable *cancellable)
+{
+       const gchar *extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+       GHashTable *known_ids; /* gchar *id ~> NULL */
+       gboolean success = FALSE;
+       GSList *groups = NULL, *link;
+       GError *error = NULL;
+
+       known_ids = m365_backend_get_known_folder_ids (m365_backend, extension_name, FALSE);
+
+       if (e_m365_connection_list_task_groups_sync (cnc, NULL, &groups, cancellable, &error) && groups) {
+               success = TRUE;
+
+               for (link = groups; link && success; link = g_slist_next (link)) {
+                       EM365TaskGroup *group = link->data;
+                       GSList *task_folders = NULL;
+
+                       if (!group)
+                               continue;
+
+                       if (e_m365_connection_list_task_folders_sync (cnc, NULL, e_m365_task_group_get_id 
(group), NULL, &task_folders, cancellable, &error)) {
+                               GSList *tlink;
+
+                               for (tlink = task_folders; tlink; tlink = g_slist_next (tlink)) {
+                                       EM365TaskFolder *task_folder = tlink->data;
+
+                                       if (!task_folder || !e_m365_task_folder_get_id (task_folder))
+                                               continue;
+
+                                       m365_backend_update_resource (m365_backend, extension_name,
+                                               e_m365_task_folder_get_id (task_folder),
+                                               e_m365_task_group_get_id (group),
+                                               e_m365_task_folder_get_name (task_folder),
+                                               e_m365_task_folder_get_is_default_folder (task_folder),
+                                               NULL);
+
+                                       g_hash_table_remove (known_ids, e_m365_task_folder_get_id 
(task_folder));
+                               }
+
+                               g_slist_free_full (task_folders, (GDestroyNotify) json_object_unref);
                        } else {
                                success = FALSE;
                        }
@@ -422,6 +487,7 @@ m365_backend_sync_calendar_folders_sync (EM365Backend *m365_backend,
        g_hash_table_destroy (known_ids);
        g_clear_error (&error);
 }
+#endif
 
 static void
 m365_backend_sync_folders_thread (GTask *task,
@@ -446,6 +512,9 @@ m365_backend_sync_folders_thread (GTask *task,
 
        if (e_source_collection_get_calendar_enabled (collection_extension)) {
                m365_backend_sync_calendar_folders_sync (m365_backend, cnc, cancellable);
+#if 0
+               m365_backend_sync_task_folders_sync (m365_backend, cnc, cancellable);
+#endif
        }
 }
 
diff --git a/src/Microsoft365/registry/e-source-m365-deltas.c 
b/src/Microsoft365/registry/e-source-m365-deltas.c
index f4a8e976..cd3a6297 100644
--- a/src/Microsoft365/registry/e-source-m365-deltas.c
+++ b/src/Microsoft365/registry/e-source-m365-deltas.c
@@ -147,7 +147,7 @@ e_source_m365_deltas_set_contacts_link (ESourceM365Deltas *extension,
        }
 
        g_free (extension->priv->contacts_link);
-       extension->priv->contacts_link = g_strdup (delta_link);
+       extension->priv->contacts_link = e_util_strdup_strip (delta_link);
 
        e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]