top of page

What Happened When I Built a Donor Nurture Agent

Martin Farrell
Jul 28
7 min read

Updated: Aug 13

I built a donor nurture agent in Agentforce Builder, in a Trailhead Playground dev org, because I wanted to test whether an agent scaffolded from a single prompt could be created quickly for a nonprofit organization.


Partway through the build, the Problems panel began shifting on its own, duplicating and reclassifying errors without any new edits being made. Underneath that sat one error I could never resolve and it blocked me from testing the agent.


Setting up the org

I provisioned the org through the "Marketing Initial Setup" flow in Salesforce Go. Data Cloud was enabled, the Marketing data kits were installed, a CRM Connector was created, a default email channel was added, and a Data Space was selected.


I clicked Generate on the Identity Resolution ruleset for Unified Individual, since the agent's donor-profile analysis later on depends on having a single, resolved record per donor.


Figure 1: Identity resolution rule sets consume data cloud credits.


Building the agent

Rather than one long prompt, I sent the build instructions in two parts, capabilities and guardrails first, then the data-source and compliance specifics.


Agentforce Panel:


Hello! How can I help you with your Agent?


Prompt:

Build an autonomous marketing assistant that analyzes donor profiles in Data Cloud to generate personalized email nurture content and CTAs.

Key Capabilities (Subagents):

1. Analyze Donor Profile: Pull from Data Cloud, run segmentation, and evaluate propensity scoring.

2. Configure Nurture Flow: Select email templates, cadence, and channels for a Segment-Triggered Flow.

3. Compose Email Content: Write personalized email copy, subject lines, and tailored CTAs.

4. Orchestrate Activation: Create/update a Segment-Triggered Flow in Marketing Cloud Next and schedule sends.

Integrations: Connect to Data Cloud segments, Calculated Insights, Marketing Cloud Next Flow Builder, Content, and custom Apex/Flows.

Guardrails: Do NOT auto-activate the Flow. Require explicit human approval before any email send or Flow activation.

Variables to Track: donor_id, segment, lifecycle_stage, persona, top_interests, last_donation_amount, last_donation_date, consent_status, flow_id.

Output Format: Provide a structured JSON plan, draft email content, and a proposed Flow map for review before creating artifacts in Marketing Cloud Next.


I've updated the following sections:

  • system: Rewrote instructions to define an autonomous nonprofit donor nurture assistant, emphasizing Data Cloud analysis, personalized content, Segment-Triggered Flow proposals, MC Next orchestration, and strict human-approval gates.

  • config: Set the description to reflect the donor nurture workflow and approval requirement.

  • variables: Added donor_id, segment, lifecycle_stage, persona, top_interests, last_donation_amount, last_donation_date, consent_status, flow_id, approval_required (default true), and approval_status (default "pending").

  • agent_router: Added routing transitions to the new subagents: analyze_donor_profile, compose_email_content, configure_nurture_flow, orchestrate_activation.

  • connections: Added placeholder connections to Data Cloud segments/insights, Marketing Cloud Next Flow Builder/Content, and custom Apex/Flows.

  • subagent analyze_donor_profile: New subagent with placeholder actions to fetch donor profile and calculated insights, set variables, and emit an analysis JSON artifact.

  • subagent compose_email_content: New subagent to ensure consent, fetch templates, draft email content/CTAs, and emit a draft_email artifact.

  • subagent configure_nurture_flow: New subagent to propose a Segment-Triggered Flow map and emit a proposed_flow artifact.

  • subagent orchestrate_activation: New subagent to create/update a draft Flow, request human approval, and output a final review package; never activates without approval.


Notes:

These new actions reference placeholder targets for external systems (Data Cloud, MC Next, custom Apex/Flows). You’ll need to implement or wire them to real Flows/Apex/Connectors in your org.


For more information on how to set up custom actions, please visit our help page.


Prompt:

For data sources, use Data Cloud DMOs: Individual__dlm, Donor_Giving_History__dll, and Unified_Engagement_Score__c. For downstream actions, have the agent configure a Segment-Triggered Flow to send against a specific donor segment, and create a high-priority Task in Sales Cloud for our major gifts team if a donor's propensity score drops. For compliance, the agent must check the HasOptedOutProcessing flag on Individual__dlm and ground its tone rules using our internal 'Nonprofit_Brand_Voice_Guidelines_2026' Knowledge article. Finally, for output scope, the agent should not autonomously publish; it must save the drafted assets as a 'Campaign_Proposal__c' custom object record for human review and orchestration.


I've updated the following sections:


  1. system: Clarified use of Data Cloud DMOs, added strict HasOptedOutProcessing and consent enforcement, grounded tone via Knowledge article, and emphasized non-activation + Campaign_Proposal__c save and Task escalation rules.

  2. config: Expanded description to reflect Data Cloud DMOs, consent checks, brand guidelines, non-activation, Campaign_Proposal__c save, and Sales Cloud Task creation trigger.

  3. variables: Ensured required fields exist and added has_opted_out_processing (boolean), propensity_score (number), and proposal_id (string).

  4. connection: Replaced invalid connections block, adding references to Data Cloud DMOs (Individual__dlm, Donor_Giving_History__dll, Unified_Engagement_Score__c), Knowledge article Nonprofit_Brand_Voice_Guidelines_2026, Marketing Cloud Next (Flow Builder, Content), and Sales Cloud (Task, Campaign_Proposal__c).

  5. agent_router: Documented routing sequence: analyze_donor_profile → compose_email_content → configure_nurture_flow → orchestrate_activation with approval gates.

  6. analyze_donor_profile: Added DMO queries (placeholders) to fetch Individual__dlm, Donor_Giving_History__dll, and Unified_Engagement_Score__c; set has_opted_out_processing and consent_status; emit analysis JSON with required fields.

  7. compose_email_content: Added consent/opt-out check to skip content if restricted; grounded tone with Knowledge article; pulls Marketing Cloud Content templates; emits draft_email with adherence note.

  8. configure_nurture_flow: Proposes a Segment-Triggered Flow with cadence/channels and entry/exit criteria; emits proposed_flow artifact (not activated).

  9. orchestrate_activation: Creates/updates a draft Segment-Triggered Flow and captures flow_id; creates a high-priority Sales Cloud Task if propensity is low; saves all assets to Campaign_Proposal__c and captures proposal_id; outputs structured plan requiring human approval.

  10. Note: Several integrations are added as placeholder actions that you can wire to your org’s Flows/Apex. For more information on how to set up custom actions, please visit our help page.

Figure 2: 9 Problems already visible. The subagent tree shows eight subagents total.


Ambiguous prompt instruction

The prompt requested a Task be created "if a donor's propensity score drops." This wording implies a comparison: the donor's current score must be checked against a prior score to detect a decline over time.


The agent isn't tracking whether the score is rising or falling over time. It's checking the current number against a fixed cutoff, 0.3. Here's the script:


if @variables.propensity_score is not None and @variables.propensity_score < 0.3


The agent did not flag this gap or ask which interpretation was intended. It selected the version that required no historical data, and proceeded.


Takeaway: ambiguous instructions don't get flagged, they get resolved with whatever's simplest to build. The prompt said "if a donor's propensity score drops" but gave no threshold, so the agent invented one: "e.g., < 0.3".


Checking the Problems panel

I opened the Problems panel to check the agent's real error count. Over the course of this build, the Errors and Warnings counts changed several times, not because new problems were introduced, but because the same underlying issues got reclassified, duplicated, or dropped from view as I saved and reopened the agent.


Figure 3: These were the errors at first: 9 total.


I saved the agent from the main editor. The Problems badge changed from 9 to 43.


Figure 4: Problems 43, Errors tab expanded, sidebar showing 43 Errors, 25 Warnings, and 11 Info together.


The jump from 9 to 43 wasn't really 34 new problems. The panel reclassified a batch of existing Warnings into Errors, and duplicated a few of them in the process, the same underlying issues showing up under a different label, and sometimes twice.


Figure 5: Problems 43, Warnings tab, still showing 25 items. This is the same content shown reclassified as Errors in Figure 4, confirming the same issues were being double-counted across categories.


I then attempted to open Preview. It was blocked, with only two options offered: "Reset Simulator" and "Review Error."


Figure 6: This is where the agent stopped being buildable and became blocked.


I clicked "Review Error." The badge remained at 43. I then clicked "Reset Simulator," followed by "Review Error" again, and the count changed once more.


Figure 7: Result: 34 Errors, 0 Warnings, 0 Info. The 8 route_type errors were present, followed immediately by the top_interests type mismatch and the get_individual_dlm, get_giving_history_dll, and get_unified_engagement_score placeholder-target messages, all now under red Error icons instead of orange Warning triangles.


Returning to the agent

I reopened the same agent in a later session, having made no edits since the last one. The Problems panel showed 9 Errors, 25 Warnings, 11 Info, the same breakdown as Figure 3, not the 34/0/0 state the prior session had ended on.


First step towards testing: fixing before_reasoning

I opened Orchestrate Activation's underlying script, using Agent Script rather than the visual canvas, because the before_reasoning error was attributed specifically to this subagent, and the canvas view offered no way to inspect or edit that field directly. I switched to the script view and searched for the field by name.


Figure 8: The before_reasoning: block on orchestrate_activation, located via in-editor search. It contained two comment lines, "Only create a draft and request human approval. Never activate or schedule" and "No deterministic steps required before reasoning," and no executable content.


I added two lines to it, because this subagent was instructed to enforce a human-approval, and nothing in the code guaranteed that gate was set before the subagent acted.


Figure 9: Two new lines added: set @variables.approval_required = True and set @variables.approval_status = "pending".


I ran into errors that could not be resolved

After saving, the before_reasoning error was gone, but the total count went from 9 to 41. Most of the new errors were the same kinds of issues that had been sitting as Warnings before, now showing up as Errors instead.


I used "Save and Refresh" instead of a plain save. That brought the count down to 8. All that remained were the 8 outbound_route_type errors.


Investigating outbound_route_type

I found the field in the agent's underlying script: 8 connections, all set to outbound_route_type: "internal".


I tried changing all 8 to "external" and "flow". I checked Salesforce's Agent Script documentation for the outbound_route_type field. The Connection Block reference page describes the field conceptually and confirms it's tied to Omni-Channel, but it doesn't include a table of valid values.


I was in a 3 day Dev Org, so at this point I did not have time to troubleshoot further. I was never able to resolve this final piece and begin testing the Agent.

 
 
 

Comments


bottom of page