diff --git a/Data/MemberData.cs b/Data/MemberData.cs
index e2bee2b..6cbf787 100644
--- a/Data/MemberData.cs
+++ b/Data/MemberData.cs
@@ -1,3 +1,5 @@
+using Remora.Rest.Core;
+
 namespace Boyfriend.Data;
 
 public class MemberData {
@@ -8,4 +10,5 @@ public class MemberData {
 
     public ulong           Id          { get; }
     public DateTimeOffset? BannedUntil { get; set; }
+    public List<Snowflake> Roles       { get; set; } = new();
 }
diff --git a/EventResponders.cs b/EventResponders.cs
index 2efe194..0f095bb 100644
--- a/EventResponders.cs
+++ b/EventResponders.cs
@@ -288,3 +288,20 @@ public class GuildScheduledEventDeleteResponder : IResponder<IGuildScheduledEven
             guildData.Configuration.EventNotificationChannel.ToDiscordSnowflake(), embeds: new[] { built }, ct: ct);
     }
 }
+
+/// <summary>
+///     Handles updating <see cref="MemberData.Roles" /> when a guild member is updated.
+/// </summary>
+public class GuildMemberUpdateResponder : IResponder<IGuildMemberUpdate> {
+    private readonly GuildDataService _dataService;
+
+    public GuildMemberUpdateResponder(GuildDataService dataService) {
+        _dataService = dataService;
+    }
+
+    public async Task<Result> RespondAsync(IGuildMemberUpdate gatewayEvent, CancellationToken ct = default) {
+        var memberData = await _dataService.GetMemberData(gatewayEvent.GuildID, gatewayEvent.User.ID, ct);
+        memberData.Roles = gatewayEvent.Roles.ToList();
+        return Result.FromSuccess();
+    }
+}
diff --git a/InteractionResponders.cs b/InteractionResponders.cs
index 6d2b729..231df31 100644
--- a/InteractionResponders.cs
+++ b/InteractionResponders.cs
@@ -23,7 +23,7 @@ public class InteractionResponders : InteractionGroup {
     ///     A button that will output an ephemeral embed containing the information about a scheduled event.
     /// </summary>
     /// <param name="state">The ID of the guild and scheduled event, encoded as "guildId:eventId".</param>
-    /// <returns>A feedback sending result which may or may not have succeeded.</returns>
+    /// <returns>An ephemeral feedback sending result which may or may not have succeeded.</returns>
     [Button("scheduled-event-details")]
     public async Task<Result> OnStatefulButtonClicked(string? state = null) {
         if (state is null) return Result.FromError(new ArgumentNullError(nameof(state)));
diff --git a/Services/Data/GuildDataService.cs b/Services/Data/GuildDataService.cs
index 4c4aa88..4552597 100644
--- a/Services/Data/GuildDataService.cs
+++ b/Services/Data/GuildDataService.cs
@@ -1,6 +1,8 @@
 using System.Text.Json;
 using Boyfriend.Data;
 using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Remora.Discord.API.Abstractions.Rest;
 using Remora.Rest.Core;
 
 namespace Boyfriend.Services.Data;
@@ -10,9 +12,14 @@ namespace Boyfriend.Services.Data;
 /// </summary>
 public class GuildDataService : IHostedService {
     private readonly Dictionary<Snowflake, GuildData> _datas = new();
+    private readonly IDiscordRestGuildAPI             _guildApi;
+    private readonly ILogger<GuildDataService>        _logger;
 
     // https://github.com/dotnet/aspnetcore/issues/39139
-    public GuildDataService(IHostApplicationLifetime lifetime) {
+    public GuildDataService(
+        IHostApplicationLifetime lifetime, IDiscordRestGuildAPI guildApi, ILogger<GuildDataService> logger) {
+        _guildApi = guildApi;
+        _logger = logger;
         lifetime.ApplicationStopping.Register(ApplicationStopping);
     }
 
@@ -75,6 +82,11 @@ public class GuildDataService : IHostedService {
             await using var dataStream = File.OpenRead(dataPath);
             var data = await JsonSerializer.DeserializeAsync<MemberData>(dataStream, cancellationToken: ct);
             if (data is null) continue;
+            var memberResult = await _guildApi.GetGuildMemberAsync(guildId, data.Id.ToDiscordSnowflake(), ct);
+            if (memberResult.IsSuccess)
+                data.Roles = memberResult.Entity.Roles.ToList();
+            else
+                _logger.LogWarning("Error in member retrieval.\n{ErrorMessage}", memberResult.Error.Message);
 
             memberData.Add(data.Id, data);
         }
@@ -91,9 +103,9 @@ public class GuildDataService : IHostedService {
         return (await GetData(guildId, ct)).Configuration;
     }
 
-    /*public async Task<MemberData> GetMemberData(Snowflake guildId, Snowflake userId, CancellationToken ct = default) {
+    public async Task<MemberData> GetMemberData(Snowflake guildId, Snowflake userId, CancellationToken ct = default) {
         return (await GetData(guildId, ct)).GetMemberData(userId);
-    }*/
+    }
 
     public IEnumerable<Snowflake> GetGuildIds() {
         return _datas.Keys;
diff --git a/Services/GuildUpdateService.cs b/Services/GuildUpdateService.cs
index b977591..dcc4d1b 100644
--- a/Services/GuildUpdateService.cs
+++ b/Services/GuildUpdateService.cs
@@ -63,7 +63,9 @@ public class GuildUpdateService : BackgroundService {
     ///     This method does the following:
     ///     <list type="bullet">
     ///         <item>Automatically unbans users once their ban period has expired.</item>
+    ///         <item>Automatically grants users the guild's <see cref="GuildConfiguration.DefaultRole"/> if one is set.</item>
     ///         <item>Sends reminders about an upcoming scheduled event.</item>
+    ///         <item>Automatically starts scheduled events if <see cref="GuildConfiguration.AutoStartEvents"/> is enabled.</item>
     ///         <item>Sends scheduled event start notifications.</item>
     ///         <item>Sends scheduled event completion notifications.</item>
     ///     </list>
@@ -73,7 +75,7 @@ public class GuildUpdateService : BackgroundService {
     ///             Downtime would affect the reliability of notifications and automatic unbans if this logic were to be in a
     ///             <see cref="IResponder{TGatewayEvent}" />.
     ///         </item>
-    ///         <item>The Discord API doesn't provide necessary about scheduled event updates.</item>
+    ///         <item>The Discord API doesn't provide necessary information about scheduled event updates.</item>
     ///     </list>
     /// </remarks>
     /// <param name="guildId">The ID of the guild to update.</param>
@@ -81,17 +83,29 @@ public class GuildUpdateService : BackgroundService {
     private async Task TickGuildAsync(Snowflake guildId, CancellationToken ct = default) {
         var data = await _dataService.GetData(guildId, ct);
         Messages.Culture = data.Culture;
+        var defaultRoleSnowflake = data.Configuration.DefaultRole.ToDiscordSnowflake();
+
+        foreach (var memberData in data.MemberData.Values) {
+            var userIdSnowflake = memberData.Id.ToDiscordSnowflake();
+            if (!memberData.Roles.Contains(defaultRoleSnowflake)) {
+                var defaultRoleResult = await _guildApi.AddGuildMemberRoleAsync(
+                    guildId, userIdSnowflake, defaultRoleSnowflake, ct: ct);
+                if (!defaultRoleResult.IsSuccess)
+                    _logger.LogWarning(
+                        "Error in automatic default role add request.\n{ErrorMessage}",
+                        defaultRoleResult.Error.Message);
+            }
 
-        // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
-        foreach (var memberData in data.MemberData.Values)
             if (DateTimeOffset.UtcNow > memberData.BannedUntil) {
                 var unbanResult = await _guildApi.RemoveGuildBanAsync(
-                    guildId, memberData.Id.ToDiscordSnowflake(), Messages.PunishmentExpired.EncodeHeader(), ct);
+                    guildId, userIdSnowflake, Messages.PunishmentExpired.EncodeHeader(), ct);
                 if (unbanResult.IsSuccess)
                     memberData.BannedUntil = null;
                 else
-                    _logger.LogWarning("Error in member data update.\n{ErrorMessage}", unbanResult.Error.Message);
+                    _logger.LogWarning(
+                        "Error in automatic user unban request.\n{ErrorMessage}", unbanResult.Error.Message);
             }
+        }
 
         var eventsResult = await _eventApi.ListScheduledEventsForGuildAsync(guildId, ct: ct);
         if (!eventsResult.IsDefined(out var events)) return;
@@ -104,9 +118,19 @@ public class GuildUpdateService : BackgroundService {
             } else {
                 var storedEvent = data.ScheduledEvents[scheduledEvent.ID.Value];
                 if (storedEvent.Status == scheduledEvent.Status) {
-                    if (DateTimeOffset.UtcNow
-                        >= scheduledEvent.ScheduledStartTime - data.Configuration.EventEarlyNotificationOffset
-                        && !storedEvent.EarlyNotificationSent) {
+                    if (DateTimeOffset.UtcNow >= scheduledEvent.ScheduledStartTime) {
+                        if (scheduledEvent.Status is not GuildScheduledEventStatus.Active) {
+                            var startResult = await _eventApi.ModifyGuildScheduledEventAsync(
+                                guildId, scheduledEvent.ID,
+                                status: GuildScheduledEventStatus.Active, ct: ct);
+                            if (!startResult.IsSuccess)
+                                _logger.LogWarning(
+                                    "Error in automatic scheduled event start request.\n{ErrorMessage}",
+                                    startResult.Error.Message);
+                        }
+                    } else if (DateTimeOffset.UtcNow
+                               >= scheduledEvent.ScheduledStartTime - data.Configuration.EventEarlyNotificationOffset
+                               && !storedEvent.EarlyNotificationSent) {
                         var earlyResult = await SendScheduledEventUpdatedMessage(scheduledEvent, data, true, ct);
                         if (earlyResult.IsSuccess)
                             storedEvent.EarlyNotificationSent = true;