#224 - Auto-delete Inactive Accounts After (x) Days

Auto-delete or deactivate member accounts after a period of inactivity.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

287 lines
Paste this into Webflow
<!-- 💙 MEMBERSCRIPT #224 v0.1 💙 AUTO-DELETE INACTIVE ACCOUNTS AFTER X DAYS(CLIENT-SIDE ONLY) -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
  var CONFIG = {
    inactivityDays: 365,
    warningDays: 30,
    gracePeriodDays: 14,
    jsonKey: "activity_tracking",
    webhookUrl: "https:comment//hook.propeu2.make.com/rrwiea1s8q7iuh9seyt6qez8jvjuj2sp",
    redirectUrl: "/",
    bannerDisplay: "block",
    modalDisplay: "flex",
    deactivatingMs: 3000,
    deactivatedMs: 4000
  };

  var memberstack = window.$memberstackDom;
  if (!memberstack) {
    console.warn("Memberscript #number224: Memberstack not found");
    return;
  }

  // ─── GET MEMBER ───

  var member;
  try {
    var memberResult = await memberstack.getCurrentMember();
    member = memberResult?.data || memberResult;
  } catch (err) {
    console.warn("Memberscript #number224: Could not get member", err);
    return;
  }
  if (!member || !member.id) return;

  var memberJSON = {};
  try {
    var jsonResult = await memberstack.getMemberJSON();
    memberJSON = jsonResult?.data || {};
  } catch (e) {}

  // ─── DOM REFERENCES ───

  var banner = document.querySelector('[data-ms-inactive="warning-banner"]');
  var modal = document.querySelector('[data-ms-inactive="deactivation-modal"]');
  if (!banner && !modal) return;

  var configEl = banner || modal;
  var inactivityDays  = parseInt(configEl.getAttribute("ms-inactive-days"))         || CONFIG.inactivityDays;
  var warningDays     = parseInt(configEl.getAttribute("ms-inactive-warning-days")) || CONFIG.warningDays;
  var gracePeriodDays = parseInt(configEl.getAttribute("ms-inactive-grace-days"))   || CONFIG.gracePeriodDays;
  var jsonKey         = configEl.getAttribute("ms-inactive-field")    || CONFIG.jsonKey;
  var webhookUrl      = configEl.getAttribute("ms-inactive-webhook")  || CONFIG.webhookUrl;
  var redirectUrl     = configEl.getAttribute("ms-inactive-redirect") || CONFIG.redirectUrl;
  var bannerDisplay   = configEl.getAttribute("ms-inactive-banner-display") || CONFIG.bannerDisplay;
  var modalDisplay    = configEl.getAttribute("ms-inactive-display")       || CONFIG.modalDisplay;

  var daysRemainingEl  = document.querySelector('[data-ms-inactive="days-remaining"]');
  var lastActiveEl     = document.querySelector('[data-ms-inactive="last-active"]');
  var acknowledgeBtn   = document.querySelector('[data-ms-inactive="acknowledge"]');
  var keepAccountBtn   = document.querySelector('[data-ms-inactive="keep-account"]');
  var closeBtns        = document.querySelectorAll('[data-ms-inactive="close"]');
  var deactivatingView = modal ? modal.querySelector('[data-ms-inactive="deactivating"]') : null;
  var deactivatedView  = modal ? modal.querySelector('[data-ms-inactive="deactivated"]')  : null;

  if (banner) banner.style.display = "none";
  if (modal) modal.style.display = "none";

  // ─── ACTIVITY TRACKING ───

  function getTracking() {
    var d = memberJSON[jsonKey];
    if (!d || typeof d !== "object") {
      return { last_active: null, warning_sent: null, warning_email_sent: false, status: "active" };
    }
    return d;
  }

  async function saveTracking(data) {
    memberJSON[jsonKey] = data;
    try {
      await memberstack.updateMemberJSON({ json: memberJSON });
    } catch (e) {
      console.warn("Memberscript #number224: Could not save tracking data", e);
    }
  }

  function daysBetween(dateA, dateB) {
    return Math.floor((dateB - dateA) / 86400000);
  }

  function formatDate(isoStr) {
    var d = new Date(isoStr);
    var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
    return months[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear();
  }

  // ─── SEND WARNING EMAIL VIA WEBHOOK ───

  async function sendWarningEmail(tracking, daysLeft) {
    if (!webhookUrl || tracking.warning_email_sent) return;
    try {
      var params = new URLSearchParams();
      params.append("memberId", member.id);
      params.append("email", member.auth?.email || "");
      params.append("action", "warning");
      params.append("daysInactive", String(daysBetween(new Date(tracking.last_active), new Date())));
      params.append("daysRemaining", String(daysLeft));
      params.append("inactivityLimit", String(inactivityDays));
      params.append("gracePeriodDays", String(gracePeriodDays));
      params.append("timestamp", new Date().toISOString());
      fetch(webhookUrl, { method: "POST", mode: "no-cors", body: params });
      tracking.warning_email_sent = true;
    } catch (e) {
      console.warn("Memberscript #number224: Warning email webhook failed", e);
    }
  }

  // ─── DEACTIVATE ACCOUNT ───

  async function deactivateAccount(tracking) {
    if (modal) {
      modal.style.display = modalDisplay;
      if (deactivatingView) deactivatingView.style.display = "";
      if (deactivatedView) deactivatedView.style.display = "none";
    }

    tracking.status = "deactivated";
    tracking.deactivated_at = new Date().toISOString();
    await saveTracking(tracking);

    if (webhookUrl) {
      try {
        var params = new URLSearchParams();
        params.append("memberId", member.id);
        params.append("email", member.auth?.email || "");
        params.append("action", "deactivate");
        params.append("daysInactive", String(daysBetween(new Date(tracking.last_active), new Date())));
        params.append("timestamp", new Date().toISOString());
        fetch(webhookUrl, { method: "POST", mode: "no-cors", body: params });
      } catch (e) {}
    }

    setTimeout(function() {
      if (modal) {
        if (deactivatingView) deactivatingView.style.display = "none";
        if (deactivatedView) deactivatedView.style.display = "";
      }

      setTimeout(async function() {
        try {
          await memberstack.logout();
        } catch (e) {}
        window.location.href = redirectUrl;
      }, CONFIG.deactivatedMs);
    }, CONFIG.deactivatingMs);
  }

  // ─── KEEP funcACCOUNT(RESET TRACKING) ───

  async function keepAccount() {
    var fresh = {
      last_active: new Date().toISOString(),
      warning_sent: null,
      warning_email_sent: false,
      status: "active"
    };
    await saveTracking(fresh);
    if (banner) banner.style.display = "none";
    if (modal) modal.style.display = "none";
  }

  // ─── MAIN LOGIC ───

  var tracking = getTracking();
  var now = new Date();

  if (!tracking.last_active) {
    tracking.last_active = now.toISOString();
    tracking.status = "active";
    await saveTracking(tracking);
    return;
  }

  var lastActiveDate = new Date(tracking.last_active);
  var daysSinceActive = daysBetween(lastActiveDate, now);
  var warningThreshold = inactivityDays - warningDays;

  if (tracking.status === "deactivated") {
    await deactivateAccount(tracking);
    return;
  }

  // Safe zone: member is active, silently refresh last_active
  if (daysSinceActive < warningThreshold) {
    if (tracking.status !== "active") {
      tracking.status = "active";
      tracking.warning_sent = null;
      tracking.warning_email_sent = false;
    }
    tracking.last_active = now.toISOString();
    await saveTracking(tracking);
    return;
  }

  // Deactivation zone: grace period has elapsed since warning
  if (tracking.warning_sent) {
    var daysSinceWarning = daysBetween(new Date(tracking.warning_sent), now);
    if (daysSinceWarning >= gracePeriodDays) {
      await deactivateAccount(tracking);
      return;
    }
  }

  // Warning zone: approaching inactivity limit
  if (tracking.status !== "warned") {
    tracking.status = "warned";
    tracking.warning_sent = now.toISOString();
  }

  var daysUntilDeactivation;
  if (tracking.warning_sent) {
    daysUntilDeactivation = gracePeriodDays - daysBetween(new Date(tracking.warning_sent), now);
  } else {
    daysUntilDeactivation = gracePeriodDays;
  }
  if (daysUntilDeactivation < 0) daysUntilDeactivation = 0;

  await sendWarningEmail(tracking, daysUntilDeactivation);
  await saveTracking(tracking);

  if (banner) {
    if (daysRemainingEl) daysRemainingEl.textContent = daysUntilDeactivation + " ";
    if (lastActiveEl) lastActiveEl.textContent = formatDate(tracking.last_active);
    banner.style.display = bannerDisplay;
  }

  // ─── EVENT HANDLERS ───

  if (acknowledgeBtn) {
    acknowledgeBtn.addEventListener("click", function(e) {
      e.preventDefault();
      keepAccount();
    });
  }

  if (keepAccountBtn) {
    keepAccountBtn.addEventListener("click", function(e) {
      e.preventDefault();
      keepAccount();
    });
  }

  closeBtns.forEach(function(btn) {
    btn.addEventListener("click", function(e) {
      e.preventDefault();
      if (banner) banner.style.display = "none";
    });
  });

  if (modal) {
    modal.addEventListener("click", function(e) {
      if (e.target === modal && tracking.status !== "deactivated") {
        modal.style.display = "none";
      }
    });
  }

  document.addEventListener("keydown", function(e) {
    if (e.key === "Escape") {
      if (banner && banner.style.display !== "none") banner.style.display = "none";
      if (modal && modal.style.display !== "none" && tracking.status !== "deactivated") {
        modal.style.display = "none";
      }
    }
  });
});
</script>
<!-- Spinner animation for the deactivating state -->
<style>
  @keyframes ms-spin {
    to { transform: rotate(360deg); }
  }

  [data-ms-inactive="warning-banner"] button:hover {
    opacity: 0.prop9;
  }
</style>

Tutorial

Download the second Make Blueprint here: https://drive.google.com/file/d/1RtEbxcF1VpaFMlCAenhUeth8vqbs_L7l/view?usp=sharing

Make.com Blueprint

Download Blueprint

Import this into Make.com to get started

Download File
How to use:
  1. Download the JSON blueprint above
  2. Navigate to Make.com and create a new scenario
  3. Click the 3-dot menu and select Import Blueprint
  4. Upload your file and connect your accounts

Script Info

Versionv0.1
PublishedApr 21, 2026
Last UpdatedApr 21, 2026

Need Help?

Join our Slack community for support, questions, and script requests.

Join Slack Community
Back to All Scripts

Related Scripts

More scripts in Custom Flows