> ## Content Index
> Fetch the complete content index at: https://blog.tsd.digital/llms.txt
> Use this file to discover other available public pages before exploring further.

# Group Newsletter Studio newsletters by month in the Umbraco backend tree
- URL: https://blog.tsd.digital/group-newsletter-studio-newsletters-by-month-in-the-umbraco-backend-tree/
- Published: 2018-01-18T13:24:06.000Z
- Updated: 2018-01-18T13:24:06.000Z
- Author: Tim Gaunt
- Tags: Umbraco, Newsletter Studio, Package, Custom Tree, Useful, #Import 2025-04-01 05:38

One of our customer's websites uses the [Newsletter Studio](https://www.newsletterstudio.org/?ref=blog.tsd.digital) Umbraco package to manage their mailing lists but had an issue because they send a lot of emails (40+ a month) so the draft/sent/archive folders were getting pretty large. We needed a way of grouping them more logically. Markus got back to me pretty quickly [on the forums](https://our.umbraco.org/projects/backoffice-extensions/newsletter-studio/comments/89163-folders-for-emails?ref=blog.tsd.digital#comment-89163) suggesting that we implement a custom tree controller and shared the [original source controller code here](https://gist.github.com/enkelmedia/648baa5587b702585193505b39b8b7fa?ref=blog.tsd.digital).

The way we've implemented it is fairly simple -and not overly efficient due to the way the data access works but I thought it worth sharing as a couple of others have asked for the code, first though here's a little screenshot:

**Before:**[](https://blog.tsd.digital/media/articulate/open-live-writer-0ef9b9a79e71a9ba-image4.png)**After:**

Drafts are listed based on the created date, sent/archived generate folders based on the sent date as that felt most logical but it wouldn't be hard to change if you didn't like it. This was just a quick "get it out there ASAP" change so I'm sure the code can be cleared up. Welcome any comments.

Installation is simple, modify the \`/config/trees.config\` file:

 <add title="Newsletters" type="YourProject.NewsletterStudio.CustomNewsletterTreeController, YourProject" iconopen="icon-message" iconclosed="icon-message" application="NewsletterStudio" alias="Newsletter" sortorder="5" initialize="true"></add>

Then you just need to add this class to your solution:

```
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http.Formatting;
using NewsletterStudio.Core.Extensions;
using NewsletterStudio.Core.Interfaces.Data;
using NewsletterStudio.Core.Model;
using NewsletterStudio.Infrastucture;
using NewsletterStudio.Infrastucture.Services;
using NewsletterStudio.Umbraco;
using umbraco.BusinessLogic.Actions;
using Umbraco.Web.Models.Trees;
using Umbraco.Web.Mvc;
using Umbraco.Web.Trees;

namespace YourProject.NewsletterStudio
{
	[PluginController(NewsletterStudioApplication.Name)]
	[Tree("NewsletterStudio", "Newsletter", "Newsletters", "icon-message", "icon-message", sortOrder: 5)]
	public class CustomNewsletterTreeController : TreeController
	{
		private INewsletterRepository _newsletterRepository;
		private LocalizationService _localization;

		public CustomNewsletterTreeController()
		{
			_newsletterRepository = GlobalFactory.Current.NewsletterRepository;
			_localization = new LocalizationService();
		}

		protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
		{
			var node = base.CreateRootNode(queryStrings);
			node.Name = _localization.Get("ns_newsletters");
			return node;
		}

		protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
		{
			var nodes = new TreeNodeCollection();

			if (id == "-1")
			{
				var nodeDraft = this.CreateTreeNode("draft", id, queryStrings, _localization.Get("ns_draft"), "icon-outbox", _newsletterRepository.CountDraft() > 0);
				nodeDraft.RoutePath = "NewsletterStudio";
				nodes.Add(nodeDraft);

				var nodeSent = this.CreateTreeNode("sent", id, queryStrings, _localization.Get("ns_sent"), "icon-mailbox", _newsletterRepository.CountSent() > 0);
				nodeSent.RoutePath = "NewsletterStudio";
				nodes.Add(nodeSent);

				var nodeArchive = this.CreateTreeNode("archive", id, queryStrings, _localization.Get("ns_archive"), "icon-file-cabinet", _newsletterRepository.CountArchive() > 0);
				nodeArchive.RoutePath = "NewsletterStudio";
				nodes.Add(nodeArchive);
			}
			else
			{
				var statusParts = new[] { id };
				var emailStatus = id;
				if (emailStatus.Contains("-"))
				{
					statusParts = emailStatus.Split('-');
					emailStatus = statusParts.FirstOrDefault();
				}

				// NOTE: This is messy as we're getting all items just to get the years/months but we don't have much of a choice atm
				IList letterList;
				switch (emailStatus)
				{
					case "draft":
						letterList = _newsletterRepository.GetDraft();
						break;
					case "sent":
						letterList = _newsletterRepository.GetSent();
						break;
					case "archive":
						letterList = _newsletterRepository.GetArchived();
						break;
					default:
						letterList = new List();
						break;
				}

				// This is a year or month tree node
				if (statusParts.Length > 1)
				{
					var subPart = statusParts[1];
					var year = Convert.ToInt32(statusParts[2]);

					var letterYears = letterList.Where(l => GetDateToGroupBy(emailStatus, l).Year == year);
					switch (subPart)
					{
						case "year":
							var months = letterYears.GroupBy(l => GetDateToGroupBy(emailStatus, l).Month);
							foreach (var month in months.OrderBy(m => m.Key))
							{
								var item = CreateTreeNode($"{emailStatus}-month-{year}-{month.Key}", id, queryStrings, CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(month.Key), "icon-folder", true);
								nodes.Add(item);
							}

							break;
						case "month":
							var monthNumber = Convert.ToInt32(statusParts[3]);
							foreach (var newsletter in letterYears.Where(l => GetDateToGroupBy(emailStatus, l).Month == monthNumber).OrderBy(l => GetDateToGroupBy(emailStatus, l)))
							{
								var item = CreateTreeNode(newsletter.Id.ToString(), id, queryStrings, newsletter.Name, "icon-message", false);

								if (newsletter.Status == NewsletterStatus.Completed)
								{
									item.Icon = "icon-pie-chart";
									item.RoutePath = "NewsletterStudio/Newsletter/analytics/" + newsletter.Id;
								}
								else
								{
									if (newsletter.ScheduledSendDate.HasValue)
										item.CssClasses.Add("ns-is-scheduled");
								}

								if (newsletter.Status == NewsletterStatus.Error)
									item.CssClasses.Add("ns-has-error");

								nodes.Add(item);
							}

							break;
					}
				}
				else
				{
					var years = letterList.GroupBy(l => GetDateToGroupBy(emailStatus, l).Year);
					foreach (var year in years.OrderBy(m => m.Key))
					{
						var item = this.CreateTreeNode($"{emailStatus}-year-{year.Key}", id, queryStrings, year.Key.ToString(), "icon-folder", true);
						nodes.Add(item);
					}
				}
			}

			return nodes;
		}

		private DateTime GetDateToGroupBy(string emailStatus, Newsletter newsletter)
		{
			if (emailStatus == "draft") return newsletter.CreatedDate;

			return newsletter.SentDate ?? newsletter.CreatedDate;
		}

		protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
		{
			var menu = new MenuItemCollection();

			if (id == "-1" || id == "draft")
			{
				menu.DefaultMenuAlias = ActionNew.Instance.Alias;
				menu.Items.Add(_localization.Get("general_create"));
			}
			else if (id.IsNumeric())
			{
				menu.Items.Add(_localization.Get("ns_copyToNew"));
				menu.Items.Add(_localization.Get("general_delete"));
			}

			menu.Items.Add(_localization.Get("actions_refreshNode"));

			return menu;
		}
	}
}
```