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

When a sprint is completed, I would like to email any users that have tasks assigned to them with a summary of all their tasks.

Ideally, I would be able to have a single rule that would essentially loop through all the users and then dynamically populate the email with the tasks that are only related to them.

A script would make it easy enough to loop through and filter all the users and tasks, but scripts can’t actually trigger emails, right?

Or can I put an entire script inside of the “Message” box in the email rule?

Just looking for a little advice on how best to accomplish something like this. :slight_smile:

1 Like

After hitting some walls on trying to make the email content itself dynamic, I’ve pivoted to trying to make smaller summaries (in a rich text field) that are unique to each user. It works if I want to send an email per summary, but I’m still just trying to create a singular weekly email that includes it all.

I thought it would be easy enough to “join” all those rich text fields together into one, big rich text field but am realizing it’s not so simple because you can only join snippets, not the full value of the field.

Based on some forum posts I’ve come across, I assume I’m going to need to use a script with “appendDocumentContent” that loops through every entity in an array and keeps adding all the rich text fields into a single one? I don’t really want to play with documents though, is there no easy way to merge them like so without the snippet limitations?

Iteration Database:

Name: Iteration 1
Assignee (1 user to many iterations): User A
Iteration Summary (Rich Text): ABC123

Name: Iteration 2
Assignee (1 user to Many iterations): User A
Iteration Summary (Rich Text): DEF456

Name: Iteration 3
Assignee (1 user to Many iterations): User A
Iteration Summary (Rich Text): GHI789

User Database:

Name: User A
Iterations (many iterations to 1 user): Iteration 1, Iteration 2, Iteration 3
Sprint Summary (Rich Text):
ABC123
DEF456
GHI789

@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.

Before getting into the weeds of markdown and scripting, I just want to check what the objective is.
You want your users to receive an email with information on their ‘active’ Iterations/Tasks, right?

Is it important to you that the email contains all the information, or could it just be a pointer to a ‘dashboard’ for the user that they could visit?
It seems to me that the User entity view can serve as a fairly good overview (dashboard) of the relevant info for any given user, especially now that it is possible to

  • set up multiple entity views - you can have one that is a ‘summary’ layout, showing all the relevant info
  • set up multiple relation views - so you can have specific filters/sorting on each relation, within a given summary layout

Could you just send each user a link to their own specific user entity summary view?

Thanks for helping!

In a perfect world, a lot of the details would be in the email itself (so they could easily copy/paste/forward the details to other people, like clients) but it’s not necessarily an absolute requirement.

Otherwise, yeah, as an alternative I could probably create a filtered dashboard of sorts on the user record and then link them to it. Is there a way to structure a link to an entity so it forces certain views/filters to load?

My goal overall is to minimize clicks and keep the user focused on only what matters to them.

Unfortunately, it’s not currently possible to specify a particular entity view layout to be opened when sending a link.

However, thinking more about this case, I wonder if you would be better served by creating a document, and embedding specific views in it, with user filters applied (e.g Assignees contains Me).
That way, you can have a single view whose URL can be sent to everyone, and each person will see different things when they open it.

1 Like

Without the ability to change filters dynamically, or add formulas to filters, I don’t see how a document would makes things any easier. The key for me is automating it so users don’t need to update the filter logic all the time.

With the new ability to pin filters, I’ve got the closest thing to what I’m looking for here:

I think I can figure out how to use a script to automatically formulate the url since it need to be different for each sprint.

Only thing missing is to be able to apply a filter within the URL, so the specific user would never need to click on their name to ensure the right filter was applied, hence my feature request here

A filter that is ‘Assignees contains Me’ is a dynamic filter - each person sees a different set of entities.

Ahhh, yeah. But you can’t have multiple assignee fields right? We’re already using the assignee field for other users so it wouldn’t work for us.

Is there any way to get the “is me” filter to work with a “user” relationship field? Or plans to allow for multiple assignee fields in a database?