Motivation

I am doing lots or recruitment at the moment - some weeks it can be near 15-25 % of my time. It is for this reason I wanted to think about my workflow and put in some automation to help me with the process.

I am currently living almost exclusively in denote - the fantastic notes package by Prot.

The real aim here is to remove as much cognitive load as possible and reduce the amount of time needed in preparation of interviews. This has the additional benefits of meaning I can provide meaningful feedback to the candidate.

The below works for our internal recruitment process - yours may vary - but the approach should be useful.

Proposed flow

  1. I have a role to fill.
  2. I create that role as a header in a centralised roles denote file.
  3. A new candidate comes in for a role.
  4. I call my function to create notes for a new candidate.
  5. The function prompts me for which role from my roles file I am working on.
  6. It prompts for the name of the candidate.
  7. A main candidate note is produced along with a feedback note.
  8. A link to the candidate note is added under the right section in my roles file.
  9. Bi-direction links are added into candidate and roles files.

I have decided to write a function to do this, I’m sure there are easier ways, but this gives me experience building up denote automation,so I think the learning here is valuable.

I suspect that more and more I will be relying on in-house made boiler-plate denote functions.

Code

First of we need to define where our roles file is - this is done by using a setq - as in the below block:

;; Set the roles file path in your Emacs configuration (init.el or config.el)
(setq TJ/roles-file "/you/path/to/Denote/2025XXXXXX--roles__role.org")

Firstly we pull out the roles from the declared roles file.

(defun TJ/get-roles-from-org-file (roles-file)
  "Extract all first-level headings (roles) from the given ROLES-FILE."
  (with-current-buffer (find-file-noselect roles-file)
    (org-element-map (org-element-parse-buffer) 'headline
      (lambda (hl)
        (when (= (org-element-property :level hl) 1)
          (org-element-property :raw-value hl))))))

Here we define behaviour to add links back into role files:

(defun TJ/insert-candidate-under-role (roles-file role candidate-file candidate-title)
  "Insert a link to the candidate note under the selected ROLE in the ROLES-FILE."
  (with-current-buffer (find-file-noselect roles-file)
    (goto-char (point-min))
    (if (search-forward (format "* %s" role) nil t)  ;; Search for the selected role
        (progn
          ;; Ensure we're at the end of the role heading line
          (end-of-line)
          ;; Now insert the link under the role heading
          (insert (format "\n[[file:%s][%s]]" candidate-file candidate-title)))  ;; Insert candidate link
      (message "Role '%s' not found in the file." role))))
	  
	  

And here we set out the flow to build out linked notes.

(defun TJ/my-denote-candidate-and-feedback ()
  "Create a Denote note for a candidate and a linked feedback note, placing the candidate under the selected role in the roles file."
  (interactive)

  ;; Step 1: Check if TJ/roles-file is set
  (unless TJ/roles-file
    (error "Roles file is not set. Please set 'TJ/roles-file' in your config."))

  ;; Step 2: Get roles from the roles file
  (let* ((roles (TJ/get-roles-from-org-file TJ/roles-file))   ;; Extract roles from the org file
         (role (completing-read "Select role: " roles nil t))  ;; Let the user select a role
         (candidate-name (read-string "Enter candidate name: "))  ;; Get candidate name
         (candidate-title (format "Candidate: %s" candidate-name))  ;; Format candidate title
         (feedback-title (format "Feedback for %s" candidate-name))  ;; Format feedback title
         (candidate-keywords '("candidate"))  ;; Keywords for candidate note
         (feedback-keywords '("feedback"))  ;; Keywords for feedback note
         (candidate-file (denote candidate-title candidate-keywords))  ;; Create candidate note
         (feedback-file (denote feedback-title feedback-keywords)))  ;; Create feedback note

    ;; Step 3: Add link to candidate note under selected role in the roles file
    (TJ/insert-candidate-under-role TJ/roles-file role candidate-file candidate-title)
    
    ;; Step 4: Add a link at the top of the candidate file pointing back to the roles file
    (with-current-buffer (find-file-noselect candidate-file)  ;; Go to the top of the file
      (insert (format "[[file:%s][Roles]]\n\n" TJ/roles-file))  ;; Insert link to roles file at the top
      (save-buffer))

    ;; Step 5: Add link to feedback note in the candidate note
    (with-current-buffer (find-file-noselect candidate-file)
      (goto-char (point-max))
      (insert "\n\n** Feedback\n")
      (insert (format "[[file:%s][%s]]" feedback-file feedback-title))  ;; Insert link to feedback
      (save-buffer))

    ;; Step 6: Add link to candidate note in the feedback note
    (with-current-buffer (find-file-noselect feedback-file)
      (goto-char (point-max))
      (insert "\n\n** Related Candidate\n")
      (insert (format "[[file:%s][%s]]" candidate-file candidate-title))  ;; Insert link to candidate
      (save-buffer))

    ;; Final message
    (message "Created candidate note and linked feedback note.")))

Next steps

This is an MVP, I have since added more to the denote notes to create templates. This could be done using the built in templates function but declaring here also works.

Tags should also be added so that requisition numbers or similar can be included.

I want to wrap this into a wider org-recruit package - something I think would be valuable to the community.

Just the code

All together it looks like this




;; Set the roles file path in your Emacs configuration (init.el or config.el)
(setq TJ/roles-file  "/you/path/to/Denote/2025XXXXXX--roles__role.org")

(defun TJ/get-roles-from-org-file (roles-file)
  "Extract all first-level headings (roles) from the given ROLES-FILE."
  (with-current-buffer (find-file-noselect roles-file)
    (org-element-map (org-element-parse-buffer) 'headline
      (lambda (hl)
        (when (= (org-element-property :level hl) 1)
          (org-element-property :raw-value hl))))))

(defun TJ/insert-candidate-under-role (roles-file role candidate-file candidate-title)
  "Insert a link to the candidate note under the selected ROLE in the ROLES-FILE."
  (with-current-buffer (find-file-noselect roles-file)
    (goto-char (point-min))
    (if (search-forward (format "* %s" role) nil t)  ;; Search for the selected role
        (progn
          ;; Ensure we're at the end of the role heading line
          (end-of-line)
          ;; Now insert the link under the role heading
          (insert (format "\n[[file:%s][%s]]" candidate-file candidate-title)))  ;; Insert candidate link
      (message "Role '%s' not found in the file." role))))

(defun TJ/my-denote-candidate-and-feedback ()
  "Create a Denote note for a candidate and a linked feedback note, placing the candidate under the selected role in the roles file."
  (interactive)

  ;; Step 1: Check if TJ/roles-file is set
  (unless TJ/roles-file
    (error "Roles file is not set. Please set 'TJ/roles-file' in your config."))

  ;; Step 2: Get roles from the roles file
  (let* ((roles (TJ/get-roles-from-org-file TJ/roles-file))   ;; Extract roles from the org file
         (role (completing-read "Select role: " roles nil t))  ;; Let the user select a role
         (candidate-name (read-string "Enter candidate name: "))  ;; Get candidate name
         (candidate-title (format "Candidate: %s" candidate-name))  ;; Format candidate title
         (feedback-title (format "Feedback for %s" candidate-name))  ;; Format feedback title
         (candidate-keywords '("candidate"))  ;; Keywords for candidate note
         (feedback-keywords '("feedback"))  ;; Keywords for feedback note
         (candidate-file (denote candidate-title candidate-keywords))  ;; Create candidate note
         (feedback-file (denote feedback-title feedback-keywords)))  ;; Create feedback note

    ;; Step 3: Add link to candidate note under selected role in the roles file
    (TJ/insert-candidate-under-role TJ/roles-file role candidate-file candidate-title)
    
    ;; Step 4: Add a link at the top of the candidate file pointing back to the roles file
    (with-current-buffer (find-file-noselect candidate-file)  ;; Go to the top of the file
      (insert (format "[[file:%s][Roles]]\n\n" TJ/roles-file))  ;; Insert link to roles file at the top
      (save-buffer))

    ;; Step 5: Add link to feedback note in the candidate note
    (with-current-buffer (find-file-noselect candidate-file)
      (goto-char (point-max))
      (insert "\n\n** Feedback\n")
      (insert (format "[[file:%s][%s]]" feedback-file feedback-title))  ;; Insert link to feedback
      (save-buffer))

    ;; Step 6: Add link to candidate note in the feedback note
    (with-current-buffer (find-file-noselect feedback-file)
      (goto-char (point-max))
      (insert "\n\n** Related Candidate\n")
      (insert (format "[[file:%s][%s]]" candidate-file candidate-title))  ;; Insert link to candidate
      (save-buffer))

    ;; Final message
    (message "Created candidate note and linked feedback note.")))