Best practice for creating a email template with content dynamic to the recipient?

@Chr1sG sorry to lean on you but could I get your advice?

Still trying to create an automated “sprint summary” email that gets populated differently depending on what things are related to the user who is receiving the email.

I’ve made it pretty far, but my solutions seem very inefficient and/or very complicated.

My first solution works, but it requires multiple rules/scripts which seems inefficient and likely to break at some point. Rule 1 generates an “iteration summary” on an iteration entity and then rule 2 appends all those iteration summaries into a big “sprint summary” for each user depending on what iterations they were related to.

Rule/Script 1: (On Sprints/Iteration database)

{!Client.Public-id!}
{!Order.Public-id!}
{!Initiative.Public-id!}
{!Release.Public-id!}
{!Public-id!}
<% const utils = context.getService(`utils`); %>

**Client:** [{{Client.Name}}](<%= utils.getEntityUrl('Clients/Client', Entity['Client.Public-Id']) %>)  
**Order:** [{{Order.Name}}](<%= utils.getEntityUrl('Clients/Order', Entity['Order.Public-Id']) %>)  
**Initiative:** [{{Initiative.Name}}](<%= utils.getEntityUrl('Clients/Initiative', Entity['Initiative.Public-Id']) %>) (Used: {{Points Utilized To Date}} Pts, Remaining: {{Points Remaining To Date}} Pts)  
**Release:** [{{Release.Name}}](<%= utils.getEntityUrl('Sprints/Release', Entity['Release.Public-id']) %>)  
**Iteration:** {{Name}} (Planned: {{Points Planned}} Pts, Logged: {{Points Logged}} Pts)  

**Iteration Summary:**
[ai temperature=0.5 maxTokens=4000 model=gpt-4o-mini]
In under 50 words, summarize the tasks ({=Tasks:Name=}) completed within this iteration of the {{Initiative.Name}} initiative.
[/ai]

**Iteration Tasks** (Name | State | Points Logged):
{- Tasks:Name, State.Name, Effort Logged -}

[**REVIEW & APPROVE ITERATION**](<%= utils.getEntityUrl(Entity.Type, Entity['Public-id']) %>)

---

Rule/Script 2: (On Fibery/User database)

const fibery = context.getService('fibery');

const user = args.currentEntities[0];

//get id of the user that triggered this automation
const iterationPO = await fibery.getEntityById('fibery/user', user.id, ['id', 'last sprint summary']);

// define the filters within the query
const iterationQuery =
`{
findIterations(
  po: {id: {is: "${user["id"]}"}}
  addToSprintSummary: {is: true}
) {
  id
  name
  weekNumber
}
}`;

// get  a response from fibery for the desired query
const response = await fibery.graphql("Sprints", iterationQuery);

// define an array from the query's response
const iterations = response.data.findIterations;

// loop through all iterations in the array
for (const iteration of iterations) {

  //get id of the iteration
  const currentIteration = await fibery.getEntityById('Sprints/Iteration', iteration.id, ['id', 'summary']);

  //get content from the iteration's summary
  const iterationSummary = await fibery.getDocumentContent(currentIteration['Summary'].secret, 'md');

  //append iteration summary to sprint summary
  await fibery.appendDocumentContent(iterationPO['Last Sprint Summary'].secret, iterationSummary, 'md');

}

My second solution was to use a single rule and try and try to create the “sprint summary” in it’s entirety all at once, which would be preferable to me. I think it would work if I could figure out how to reference the values of the javascript variables into the markdown that needs to be written, and be able to force line breaks. Is that even possible? Everything I’ve tried just results in the variables names being rendered as rich text instead of their values actually populating dynamically.

Script: (On Fibery/User database)

const fibery = context.getService('fibery');
const utils = context.getService('utils');

const user = args.currentEntities[0];

//get id of the user that triggered this automation
const currentUser = await fibery.getEntityById('fibery/user', user.id, ['id', 'last sprint summary']);

// define the filters within the query
const iterationQuery =
`{
  findIterations(
  po: {id: {is: "${user["id"]}"}}
  addToSprintSummary: {is: true}
  ) {
    id
    publicId
    name
    pointsPlanned
    pointsLogged
    state {
      name
      id
    }
    client {
      id
      publicId
      name
    }
    initiative {
      id
      publicId
      name
      pointsAllocated
      pointsAvailable
    }
    release {
      id
      publicId
      name
    }
    order {
      id
      publicId
      name
    }
    tasks {
      id
      publicId
      name
      state {
        id
        name
      }
      points(
        pointType: {name: {is: "Log"}}
      ) {
        id
        publicId
        name
        points
        pointType {
          id
          publicId
          name
        }
      }
    }
  }
}`;

// get a response from fibery for the desired query
const response = await fibery.graphql("Sprints", iterationQuery);

// define an array from the query's response
const iterations = response.data.findIterations;

// loop through all iterations in the array
for (const iteration of iterations) {

  const clientURL = utils.getEntityUrl('Clients/Client', iteration.client.publicId);
  const orderURL = utils.getEntityUrl('Clients/Order', iteration.order.publicId);
  const initativeURL = utils.getEntityUrl('Clients/Initiative', iteration.initiative.publicId);
  const releaseURL = utils.getEntityUrl('Sprints/Release', iteration.release.publicId);
  const iterationURL = utils.getEntityUrl('Sprints/Iteration', iteration.publicId);

  //create summary in markdown
  const sprintSummary =
    'TESTING123  ** Client:** [<%= iteration.client.name %>](<%= clientURL %>) ** Order:** [<%= iteration.order.name %>](<%= orderURL %>) %> '

  //append iteration summary to sprint summary
  await fibery.setDocumentContent(currentUser['Last Sprint Summary'].secret, sprintSummary, 'md');

}

I’m sure there’s a better way overall to do this, but after trying so many things my brain’s a bit scrambled. I had the brilliant idea of doing it even easier and cleaner with a notification but would need to be able to reference the notification recipient as a dynamic variable so the iterations could be filtered on it.