# How the Markdown Here "Forgot-to-Render" Detection and Warning System Works

> Learn how the Markdown Here forgot-to-render detection and warning system intercepts emails, using polling and pattern matching to prevent sending unrendered Markdown.

- Repository: [Adam Pritchard/markdown-here](https://github.com/adam-p/markdown-here)
- Tags: internals
- Published: 2026-03-05

---

**Markdown Here uses a periodic polling mechanism combined with heuristic pattern matching to detect raw Markdown in email compose windows, intercepting send actions to display a modal warning when users attempt to send unrendered content.**

The **forgot-to-render detection** system in the `adam-p/markdown-here` repository prevents users from accidentally sending raw Markdown syntax instead of formatted HTML. This feature monitors supported webmail clients like Gmail and Fastmail, analyzing compose window content every two seconds to identify unrendered Markdown before interception occurs.

## Architecture Overview

The system distributes responsibilities across three core modules to respect browser security models while maintaining clean separation of concerns.

- **[`src/chrome/contentscript.js`](https://github.com/adam-p/markdown-here/blob/main/src/chrome/contentscript.js)**: Runs the periodic 2-second interval check on focused compose elements
- **[`src/common/common-logic.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/common-logic.js)**: Implements detection heuristics, installs send-button interceptors, and renders the warning modal
- **[`src/chrome/backgroundscript.js`](https://github.com/adam-p/markdown-here/blob/main/src/chrome/backgroundscript.js)**: Supplies the prompt HTML template via privileged script messaging

## The Periodic Detection Loop

The content script initiates monitoring by loading user preferences and scheduling regular checks.

```javascript
// src/chrome/contentscript.js
let forgotToRenderIntervalCheckPrefs = null;

// Load preferences including the "forgot-to-render" toggle
Utils.makeRequestToPrivilegedScript(document,
                                   {action: 'get-options'},
                                   function(prefs){ 
                                     forgotToRenderIntervalCheckPrefs = prefs; 
                                   });

function forgotToRenderCheck() {
  if (!forgotToRenderIntervalCheckPrefs ||
      !forgotToRenderIntervalCheckPrefs['forgot-to-render-check-enabled-2']) {
    return;
  }

  let focusedElem = markdownHere.findFocusedElem(window.document);
  if (!focusedElem) return;

  // Delegate detection to CommonLogic
  CommonLogic.forgotToRenderIntervalCheck(
        focusedElem, markdownHere, MdhHtmlToText, marked,
        forgotToRenderIntervalCheckPrefs);
}

setInterval(forgotToRenderCheck, 2000);

```

The interval only executes when the user enables `forgot-to-render-check-enabled-2` in the extension options.

## Heuristic Markdown Detection

The `CommonLogic.forgotToRenderIntervalCheck` function in [`src/common/common-logic.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/common-logic.js) orchestrates the detection pipeline.

```javascript
// src/common/common-logic.js
function forgotToRenderIntervalCheck(focusedElem, MarkdownHere, MdhHtmlToText, marked, prefs) {
  if (!prefs['forgot-to-render-check-enabled-2']) return;

  // 1. Find the Send button selector for the current webmail client
  var forgotToRenderButtonSelector = getForgotToRenderButtonSelector(focusedElem);
  if (!forgotToRenderButtonSelector) return;

  // 2. Verify the element can be rendered
  if (!MarkdownHere.elementCanBeRendered(focusedElem)) return;

  // 3. Install interceptors once per compose element
  if (typeof(focusedElem[WATCHED_PROPERTY]) === 'undefined') {
    setupForgotToRenderInterceptors(focusedElem, MdhHtmlToText, marked, prefs);
    focusedElem[WATCHED_PROPERTY] = true;
  }
}

```

### Pattern Matching Logic

The `probablyWritingMarkdown` function scans the raw HTML content for typical Markdown syntax patterns.

- Bullet lists (`- `, `* `, `1. `)
- Code backticks (`` ` `` or ``` `` ```)
- Math delimiters (`$...$`)
- Header markers (`# `)

- Emphasis syntax (`**bold**`, `*italic*`)
- Link syntax (`[text](url)`)

If any pattern matches, the system flags the content as potentially unrendered Markdown requiring user confirmation.

## Intercepting the Send Action

Once detection confirms the environment supports interception, `setupForgotToRenderInterceptors` attaches event listeners to capture send attempts before the webmail client processes them.

```javascript
// src/common/common-logic.js
function setupForgotToRenderInterceptors(composeElem, MdhHtmlToText, marked, prefs) {
  var composeSendButton = findClosestSendButton(composeElem);
  
  // Determine if content looks like Markdown
  var shouldIntercept = function() {
    var mdMaybe = new MdhHtmlToText.MdhHtmlToText(composeElem, null, true).get();
    return probablyWritingMarkdown(mdMaybe, marked, prefs);
  };

  // Attach listeners to the Send button's parent container
  composeSendButton.parentNode.addEventListener('keydown', composeSendButtonKeyListener, true);
  composeSendButton.parentNode.addEventListener('keyup', composeSendButtonKeyListener, true);
  composeSendButton.parentNode.addEventListener('click', composeSendButtonClickListener, true);
  
  // Capture Ctrl/Cmd+Enter hotkeys on the compose area
  composeElem.parentNode.addEventListener('keydown', sendHotkeyKeydownListener, true);
}

```

The listeners call `eatEvent(event)` to stop propagation of the original send command, then invoke `showForgotToRenderPromptAndRespond` to present the user with a choice.

## Displaying the Warning Prompt

Because content scripts cannot access local files directly due to Chrome's Content Security Policy, the system requests the modal HTML from the privileged background script.

```javascript
// src/common/common-logic.js
function showForgotToRenderPromptAndRespond(composeElem, composeSendButton) {
  Utils.makeRequestToPrivilegedScript(
    composeElem.ownerDocument,
    {action: 'get-forgot-to-render-prompt'},
    function(response){
      showHTMLForgotToRenderPrompt(response.html, composeElem,
                                   composeSendButton, sendOrGoBackToCompose);
    });
}

```

The background script loads [`src/common/forgot-to-render-prompt.html`](https://github.com/adam-p/markdown-here/blob/main/src/common/forgot-to-render-prompt.html), injects localized strings via `Utils.getMessage`, and returns the final markup:

```javascript
// src/chrome/backgroundscript.js
else if (request.action === 'get-forgot-to-render-prompt') {
  CommonLogic.getForgotToRenderPromptContent(function(html){
    responseCallback({html: html});
  });
}

```

### Modal Injection and Keyboard Handling

The `showHTMLForgotToRenderPrompt` function injects the HTML into the page body, manages focus, and implements custom keyboard navigation.

- **Escape key**: Cancels the send and returns to the compose window
- **Tab key**: Cycles focus between the "Back to Safety" and "Send Anyway" buttons
- **Enter/Space**: Activates the focused button (with debouncing to prevent double-triggering)

When the user selects **Send Anyway**, the callback executes `Utils.fireMouseClick(composeSendButton)`, programmatically triggering the original Gmail send flow. Selecting **Back to Safety** refocuses the compose element without sending.

## Enabling and Debugging the Feature

Users activate the detection system via the options page checkbox controlling the `forgot-to-render-check-enabled-2` preference.

Developers can manually trigger the detection pipeline for debugging:

```javascript
// Console execution in Gmail compose window
CommonLogic.forgotToRenderIntervalCheck(
    markdownHere.findFocusedElem(document),
    markdownHere,
    MdhHtmlToText,
    marked,
    { 'forgot-to-render-check-enabled-2': true }
);

```

## Summary

- **Periodic polling**: The content script checks focused compose elements every 2 seconds via `setInterval`
- **Heuristic detection**: `probablyWritingMarkdown` analyzes HTML content for Markdown syntax patterns before allowing sends
- **Event interception**: `setupForgotToRenderInterceptors` captures clicks and keyboard shortcuts (Ctrl/Cmd+Enter) on the Send button
- **Privileged messaging**: The background script supplies the modal HTML template because content scripts cannot read local files directly
- **User choice**: The modal offers "Back to Safety" (refocus compose) or "Send Anyway" (fire original click event)

## Frequently Asked Questions

### How does Markdown Here detect that I'm writing Markdown and not just plain text?

The extension uses the `probablyWritingMarkdown` function in [`src/common/common-logic.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/common-logic.js) to scan the compose element's HTML for specific syntax patterns including bullet lists, backticks for code, header markers, emphasis asterisks, and link brackets. If any Markdown-specific patterns are detected, the system assumes you intended to render the content before sending.

### What happens if I disable the forgot-to-render check in the options?

When the `forgot-to-render-check-enabled-2` preference is set to false, the `forgotToRenderCheck` function in [`src/chrome/contentscript.js`](https://github.com/adam-p/markdown-here/blob/main/src/chrome/contentscript.js) returns immediately without calling `CommonLogic.forgotToRenderIntervalCheck`. No interceptors are installed on the Send button, and the email client processes send commands normally without displaying the warning modal.

### Why does the extension need to communicate with the background script just to show a warning dialog?

Chrome's extension architecture restricts content scripts from reading local HTML files directly due to Content Security Policy limitations. The `get-forgot-to-render-prompt` action in [`src/chrome/backgroundscript.js`](https://github.com/adam-p/markdown-here/blob/main/src/chrome/backgroundscript.js) reads [`src/common/forgot-to-render-prompt.html`](https://github.com/adam-p/markdown-here/blob/main/src/common/forgot-to-render-prompt.html), processes localization placeholders like `{{forgot_to_render_prompt_title}}`, and returns the final HTML string to the content script for injection into the page DOM.

### Can the forgot-to-render detection work with keyboard shortcuts like Ctrl+Enter?

Yes. The `setupForgotToRenderInterceptors` function explicitly attaches a `sendHotkeyKeydownListener` to the compose element's parent node to capture Ctrl+Enter (Windows/Linux) or Cmd+Enter (macOS) combinations. This ensures the warning appears whether you click the Send button or use keyboard shortcuts to send the email.