groupMap = {};
+
+ foreach CardSummary card in cards {
+ string[] groupKeys = [];
+
+ match summaryConfig.grouping {
+ LIST => {
+ groupKeys.push(card.listName);
+ }
+ MEMBER => {
+ if card.members.length() > 0 {
+ groupKeys = card.members;
+ } else {
+ groupKeys.push("Unassigned");
+ }
+ }
+ LABEL => {
+ if card.labels.length() > 0 {
+ groupKeys = card.labels;
+ } else {
+ groupKeys.push("No Labels");
+ }
+ }
+ }
+
+ foreach string groupKey in groupKeys {
+ if !groupMap.hasKey(groupKey) {
+ groupMap[groupKey] = [];
+ }
+ CardSummary[] existingCards = groupMap.get(groupKey);
+ existingCards.push(card);
+ groupMap[groupKey] = existingCards;
+ }
+ }
+
+ GroupedSummary[] groupedSummaries = [];
+ foreach string groupName in groupMap.keys() {
+ CardSummary[] groupCardsList = groupMap.get(groupName);
+ groupedSummaries.push({
+ groupName: groupName,
+ cards: groupCardsList
+ });
+ }
+
+ return groupedSummaries;
+}
+
+function getFormattedTimestamp(time:Utc utcTime) returns string {
+ time:Civil civil = time:utcToCivil(utcTime);
+ return string `${civil.year}-${civil.month.toString().padZero(2)}-${civil.day.toString().padZero(2)} ${civil.hour.toString().padZero(2)}:${civil.minute.toString().padZero(2)} UTC`;
+}
+
+function escapeHtml(string value) returns string {
+ string[] parts = [];
+ foreach int i in 0 ..< value.length() {
+ string ch = value.substring(i, i + 1);
+ match ch {
+ "&" => {
+ parts.push("&");
+ }
+ "<" => {
+ parts.push("<");
+ }
+ ">" => {
+ parts.push(">");
+ }
+ "\"" => {
+ parts.push(""");
+ }
+ "'" => {
+ parts.push("'");
+ }
+ _ => {
+ parts.push(ch);
+ }
+ }
+ }
+ return string:'join("", ...parts);
+}
+
+function getHtmlFormattedGroup(GroupedSummary group) returns string {
+ string cardsHtml = "";
+ foreach CardSummary card in group.cards {
+ string cardNameEscaped = escapeHtml(card.name);
+ string cardUrlEscaped = escapeHtml(card.url);
+ string leftBorder = card.isOverdue
+ ? " style=\"border-left: 3px solid #de350b; padding: 16px 30px; border-bottom: 1px solid #e1e4e8;\""
+ : (card.isStale
+ ? " style=\"border-left: 3px solid #ff8b00; padding: 16px 30px; border-bottom: 1px solid #e1e4e8;\""
+ : " style=\"padding: 16px 30px; border-bottom: 1px solid #e1e4e8;\"");
+
+ string dueDateHtml = "";
+ time:Civil? dueDate = card.dueDate;
+ if dueDate is time:Civil {
+ string dueDateStr = string `${dueDate.year}-${dueDate.month.toString().padZero(2)}-${dueDate.day.toString().padZero(2)}`;
+ string dueDateColor = card.isOverdue ? "#de350b" : "#586069";
+ dueDateHtml = string `Due: ${dueDateStr} `;
+ }
+
+ string labelsHtml = "";
+ foreach string label in card.labels {
+ labelsHtml += string `${escapeHtml(label)}`;
+ }
+
+ string membersHtml = "";
+ foreach string member in card.members {
+ membersHtml += escapeHtml(member) + " ";
+ }
+
+ string cardAgeHtml = summaryConfig.showCardAge
+ ? string `Age: ${card.cardAgeDays.toString()} days ` : "";
+
+ string attachmentsHtml = summaryConfig.showAttachmentCount && card.attachmentCount > 0
+ ? string `Attachments: ${card.attachmentCount.toString()} ` : "";
+
+ string checklistHtml = "";
+ if summaryConfig.showChecklistProgress && card.checklistItemsTotal > 0 {
+ string pct = formatPercentageTwoDecimals(card.checklistCompletionPercentage);
+ checklistHtml = string `Checklist: ${card.checklistItemsCompleted.toString()}/${card.checklistItemsTotal.toString()} (${pct}%)`;
+ }
+
+ string descHtml = "";
+ if card.description.trim().length() > 0 {
+ string truncatedDesc = card.description.length() > 200
+ ? card.description.substring(0, 200) + "..." : card.description;
+ descHtml = string `${escapeHtml(truncatedDesc)}
`;
+ }
+
+ string overdueTag = summaryConfig.highlightOverdueCards && card.isOverdue
+ ? "OVERDUE"
+ : "";
+
+ string metaHtml = dueDateHtml + cardAgeHtml + attachmentsHtml + checklistHtml;
+ string labelsRow = labelsHtml.length() > 0 ? string `${labelsHtml}
` : "";
+ string membersRow = membersHtml.trim().length() > 0
+ ? string `Members: ${membersHtml.trim()}
` : "";
+
+ cardsHtml += string `
+
+ |
+ ${cardNameEscaped}${overdueTag}
+ ${metaHtml}
+ ${labelsRow}
+ ${membersRow}
+ ${descHtml}
+ |
+
`;
+ }
+
+ return string `
+
+ |
+ ${escapeHtml(group.groupName)}
+ (${group.cards.length().toString()} cards)
+ |
+
+
+ |
+
+ |
+
`;
+}
+
+function formatPercentageTwoDecimals(decimal value) returns string {
+ decimal rounded = value.round(2);
+ int wholePart = rounded;
+ int decimalPart = ((rounded - wholePart) * 100);
+
+ return string `${wholePart.toString()}.${decimalPart.toString().padZero(2)}`;
+}
+
+function generateEmailContent(GroupedSummary[] groupedSummaries, int totalCards, int overdueCount, int staleCount) returns string {
+ string groupedByStr = summaryConfig.grouping.toString();
+ string generatedAt = getFormattedTimestamp(time:utcNow());
+
+ string groupsHtml = "";
+ foreach GroupedSummary group in groupedSummaries {
+ groupsHtml += getHtmlFormattedGroup(group);
+ }
+
+ string overdueCountDisplay = summaryConfig.highlightOverdueCards ? overdueCount.toString() : "-";
+
+ return string `
+
+
+
+
+
+ Trello Cards Summary
+
+
+
+
+
+
+ Trello Cards Summary: ${totalCards.toString()} total, ${overdueCount.toString()} overdue, grouped by ${groupedByStr}
+
+
+
+
+ | |
+
+
+
+
+
+ |
+ Trello Cards Summary
+ Grouped by ${groupedByStr} • ${generatedAt}
+ |
+
+
+ |
+
+
+
+
+
+
+ |
+ ${totalCards.toString()}
+ Total Cards
+ |
+
+ ${overdueCountDisplay}
+ Overdue
+ |
+
+ ${staleCount.toString()}
+ Stale
+ |
+
+
+ |
+
+
+ ${groupsHtml}
+
+
+ |
+ You are receiving this Trello summary because it was scheduled via the Trello Summary Email integration.
+
+ Unsubscribe
+ •
+ Update preferences
+
+ |
+
+
+ | |
+
+
+
+
+`;
+}
+
+function sendEmailSummary(string htmlContent) returns error? {
+ string subject = mailchimpConfig.subjectPrefix;
+ if mailchimpConfig.includeDateInSubject {
+ time:Civil currentDate = time:utcToCivil(time:utcNow());
+ string dateStr = string `${currentDate.year}-${currentDate.month.toString().padZero(2)}-${currentDate.day.toString().padZero(2)}`;
+ subject = string `${mailchimpConfig.subjectPrefix} - ${dateStr}`;
+ }
+
+ mailchimp:Campaign1 campaign = check mailchimpClient->postCampaigns({
+ 'type: "regular",
+ recipients: {
+ list_id: mailchimpConfig.listId
+ },
+ settings: {
+ subject_line: subject,
+ from_name: mailchimpConfig.fromName,
+ reply_to: mailchimpConfig.fromAddress,
+ title: subject
+ }
+ });
+
+ string? campaignId = campaign?.id;
+ if campaignId is () {
+ return error("Failed to create campaign: Campaign ID is null");
+ }
+
+ _ = check mailchimpClient->putCampaignsIdContent(
+ campaignId = campaignId,
+ payload = {
+ html: htmlContent
+ }
+ );
+
+ _ = check mailchimpClient->postCampaignsIdActionsSend(campaignId = campaignId);
+}
+
+function countOverdueCards(CardSummary[] cards) returns int {
+ int count = 0;
+ foreach CardSummary card in cards {
+ if card.isOverdue {
+ count += 1;
+ }
+ }
+ return count;
+}
+
+function countStaleCards(CardSummary[] cards) returns int {
+ int count = 0;
+ foreach CardSummary card in cards {
+ if card.isStale {
+ count += 1;
+ }
+ }
+ return count;
+}
diff --git a/integrator-default-profile/samples/trello-summary-email/main.bal b/integrator-default-profile/samples/trello-summary-email/main.bal
new file mode 100644
index 00000000..fca4a603
--- /dev/null
+++ b/integrator-default-profile/samples/trello-summary-email/main.bal
@@ -0,0 +1,9 @@
+import ballerina/log;
+
+public function main() returns error? {
+ log:printInfo("Starting Trello Card Summary Automation");
+ log:printInfo(string `Grouping: ${summaryConfig.grouping.toString()}`);
+ log:printInfo(string `Mailchimp List: ${mailchimpConfig.listId}`);
+
+ check sendTrelloSummary();
+}
diff --git a/integrator-default-profile/samples/trello-summary-email/types.bal b/integrator-default-profile/samples/trello-summary-email/types.bal
new file mode 100644
index 00000000..2a2c6f56
--- /dev/null
+++ b/integrator-default-profile/samples/trello-summary-email/types.bal
@@ -0,0 +1,31 @@
+import ballerina/time;
+
+public enum SummaryGrouping {
+ LIST,
+ MEMBER,
+ LABEL
+}
+
+public type CardSummary record {|
+ string id;
+ string name;
+ string listName;
+ string boardName;
+ string url;
+ string[] labels;
+ string[] members;
+ time:Civil? dueDate;
+ boolean isOverdue;
+ string description;
+ int cardAgeDays;
+ boolean isStale;
+ int attachmentCount;
+ int checklistItemsTotal;
+ int checklistItemsCompleted;
+ decimal checklistCompletionPercentage;
+|};
+
+public type GroupedSummary record {|
+ string groupName;
+ CardSummary[] cards;
+|};