<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Sending an “inactive account, will be marked dormant” email in FusionAuth]]></title><description><![CDATA[<p dir="auto">I want to email users after 55 days of inactivity, warn them they’ll be marked dormant in 5 days, and then mark them dormant.</p>
<p dir="auto">Does FusionAuth do this out of the box?</p>
]]></description><link>https://fusionauth.io/community/forum/topic/3280/sending-an-inactive-account-will-be-marked-dormant-email-in-fusionauth</link><generator>RSS for Node</generator><lastBuildDate>Thu, 22 Oct 2026 21:36:18 GMT</lastBuildDate><atom:link href="https://fusionauth.io/community/forum/topic/3280.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 22 Oct 2026 19:36:17 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Sending an “inactive account, will be marked dormant” email in FusionAuth on Thu, 22 Oct 2026 19:44:27 GMT]]></title><description><![CDATA[<p dir="auto">Not as a single feature, but doable with some glue code. FusionAuth has no built-in “inactivity warning” workflow or scheduler for this.</p>
<p dir="auto">It does give you the pieces: a lastLoginInstant on every user, a user search API, an email send API, and user deactivation. You wire them together with a small scheduled job (cron, Lambda, GitHub Action, etc.) outside FusionAuth.</p>
<p dir="auto">Set up a daily job that does three things:</p>
<ul>
<li>Finds users whose last activity was about 55 days ago and who haven’t been warned yet.</li>
<li>Sends them the warning email and records that you did.</li>
<li>Finds users who are 60+ days inactive and marks them dormant.</li>
</ul>
<p dir="auto">If a user logs in during the 5-day window, their lastLoginInstant updates and they drop out of the step 3 query automatically.</p>
<p dir="auto">Use the User Search API with an Elasticsearch/OpenSearch query on lastLoginInstant (milliseconds since epoch). This needs the Elasticsearch/OpenSearch search engine. The database search engine doesn’t support range queries on this field.</p>
<pre><code>POST /api/user/search
{
  "search": {
    "numberOfResults": 500,
    "query": "{\"bool\":{\"must\":[{\"range\":{\"lastLoginInstant\":{\"lt\":1760000000000}}}],\"must_not\":[{\"term\":{\"data.dormantWarningSent\":true}}]}}"
  }
}
</code></pre>
<p dir="auto">Compute the lt value in your job as now - 55 days (so replace <code>1760000000000</code>). Users who have never logged in won’t have a <code>lastLoginInstant</code>, so decide whether to fall back to <code>insertInstant</code> for them.</p>
<p dir="auto">You can use FusionAuth to send the email or your own system. If you choose the former, create an email template in <code>Customizations → Email Templates</code>, then call the <code>Send Email API</code> with the matching user IDs:</p>
<pre><code>POST /api/email/send/{emailTemplateId}
{
  "userIds": ["&lt;id1&gt;", "&lt;id2&gt;"],
  "requestData": {
    "daysInactive": 55,
    "daysUntilDormant": 5
  }
}
</code></pre>
<p dir="auto">Your template body can then use FreeMarker:</p>
<pre><code>Hi ${user.firstName!"there"},

As part of our routine account security measures, we monitor accounts for
inactivity. We haven't detected activity on your account in the last
${daysInactive} days, and it is scheduled to be marked dormant in
${daysUntilDormant} days unless activity is detected.

Log in to keep your account active: https://login.example.com
</code></pre>
<p dir="auto">requestData values are available at the top level of the template, so you can reuse the template if you change the thresholds later.</p>
<p dir="auto">After a successful send, patch the user to record that they've been notified so you don't send them multiple emails:</p>
<pre><code>PATCH /api/user/{userId}
{
  "user": { "data": { "dormantWarningSent": true, "dormantWarningSentAt": 1760000000000 } }
}
</code></pre>
<p dir="auto">The <code>must_not</code> clause in the search query above then skips them. If the user logs in, clear the flag so they can be warned again in the future. A <code>user.login.success</code> webhook receiver that uses the API is a clean place to do that.</p>
<p dir="auto">As far as what you actually do when the user is marked dormant, you have two options:</p>
<ul>
<li>Deactivate the user. <code>DELETE /api/user/{id}</code> (soft delete) sets <code>active=false</code>, which blocks login. To reactivate, call <code>PUT /api/user/{id}?reactivate=true</code>. Deactivation also fires a <code>user.deactivate</code> event that you can consume via webhook to notify downstream systems.</li>
<li>Flag only. Set <code>user.data.dormant = true</code> and handle it in your app where you might do a step-up or force an account recovery. This is gentler and keeps the account usable after a step-up.</li>
</ul>
<p dir="auto">For a true “close the account” outcome, deactivate first. Hard-delete (<code>?hardDelete=true</code>) only after a retention period that satisfies your compliance requirements.</p>
<p dir="auto">Caveats:</p>
<p dir="auto"><code>lastLoginInstant</code> updates when a user authenticates. Silent token refreshes using an existing refresh token don’t necessarily count as logins, so a user with a long-lived session in your app could look inactive. If that matters, track your own activity timestamp in <code>user.data</code> (updated by your app or a webhook) and query that instead. Test this against your own setup before relying on it.</p>
<p dir="auto"><code>user.lastLoginInstant</code> is global to the user within a tenant. Each registration also has its own <code>lastLoginInstant</code> (<code>user.registrations[].lastLoginInstant</code>). If “inactive” should mean inactive in a specific application, query on that field and filter by <code>applicationId</code>.</p>
<p dir="auto">Run the job daily and use a window, not an exact day match, so a missed run doesn’t skip anyone.</p>
<p dir="auto">Page through search results (startRow) rather than assuming one page covers everyone.</p>
<p dir="auto">Test with a user whose lastLoginInstant you’ve backdated in a non-production tenant.</p>
<p dir="auto">Make sure your Send Email API key has only the endpoints it needs (search, send email, patch user, deactivate).</p>
]]></description><link>https://fusionauth.io/community/forum/post/8898</link><guid isPermaLink="true">https://fusionauth.io/community/forum/post/8898</guid><dc:creator><![CDATA[dan]]></dc:creator><pubDate>Thu, 22 Oct 2026 19:44:27 GMT</pubDate></item></channel></rss>