This is the third post from my n8n intake classifier, a practice project for a fictional automation agency that I built with an AI collaborator. The first was about the classification prompt. The second was about ambiguous, invalid and system_error. This one is about the wiring after classification: where logging goes, what the Gmail node does to your data, and a bug I only found by opening real inboxes. The workflow is in the GitHub repository.
Where logging was supposed to go
After classification, a Switch node sends each submission down one of five paths, and each path sends emails. Every submission also gets logged to Google Sheets. For this phase I planned to work the way I had in the earlier ones: Sheets first, then emails, one branch at a time.
Before building anything, I caught a conflict. Logging is the same logic on all five paths, and only the data changes. Putting it first inside each branch meant five copies of one node, or converging after Sheets and then branching again to send category-specific emails. Neither made sense. So I flipped the order: build the emails first, since those differ per category, and let all five paths converge on one shared Sheets node at the end.
Gmail replaces your data
The first branch I built showed a second problem. When the Gmail node sends an email, the item it passes on is Gmail's own send confirmation, not the submission. Name, Email and description were gone by the time anything downstream needed them. The LLM Chain node had done the same thing to me earlier.
In the ambiguous/system_error branch, I added a Set node right after the internal notification to rebuild one clean object. The IF node and both client emails read from that, instead of each reaching back to whichever earlier node had the data. In simple_automation, I added a Set node after the client email to rebuild the object before it reached the shared Sheets node.
Noticing I'd solved it twice
While wiring the final Sheets node, I looked at the ambiguous/system_error branch and saw it fed Sheets from two points off the same rebuild node, one path through the client emails and one straight to Sheets. That wasn't a bug. The email path never reconnected to Sheets, so nothing was logged twice. But it showed me I had solved the Gmail problem two different ways. Everywhere else I rebuilt the data after the email. In this branch I branched before the email, because Sheets never needed anything the email step produced.
Moving logging upstream
That comparison pointed at something bigger. Every branch converged on one Sheets node, so there was no reason logging should depend on any email succeeding. A Gmail send can fail for reasons that have nothing to do with the submission.
I moved the Sheets connection to the two points right after classification: one for a successful classification and one for the failure handler. Both now feed the Switch and Sheets in parallel. Two side effects followed. has_partial_intent only had to be added in two places instead of four, and I could delete the end-of-branch Set nodes that existed only to feed the old Sheets wiring.
Before deleting any of them, I checked each one. Some still fed a client email's own data, and removing one of those would have broken a client-facing email without touching Sheets. I kept those.
The final build still contains both patterns. The ambiguous/system_error branch has its rebuild node, and the other client emails reach back to earlier nodes by name:
={{ $('Route: By Classification Category').item.json.Email }}
I haven't compared whether one pattern is more robust than the other. What changed is that logging no longer depends on either.
Checking real inboxes
After the rewiring, I retested every branch individually and checked real inboxes, not just node output. That caught a bug in the ai_workflow branch: the internal notification and the client email had their destinations swapped. The internal-only message went to the client, and the client's confirmation went to the team.
Both emails sent without errors, so nothing had failed that would make me suspect a problem. Only who received what showed it. I then confirmed the simple_automation branch had no swap, since I'd used the same inbox check there.
My notes don't record how the swap got there, or whether the rewiring caused it, so I won't guess. The current build sends each internal email to the team address and each client email to the submitter.
A test case I wrote wrong
While testing ambiguous, my first case was a deliberately vague description. It came back as invalid, and I took that as a bug. Reading the reasoning showed the classifier was right. Vague wording alone doesn't give any category a specific signal to rest on, which is a different situation from two categories each being concretely supported. I wrote a sharper case with explicit signals for both ai_workflow and custom_integration, and that one returned ambiguous.
What this doesn't show
Every test used descriptions I wrote. A forced classification failure does produce a row in the sheet, with the has_partial_intent cell empty, because the model never produced a value on that path. I did not test what happens if the Sheets step itself fails.
What I take from it is narrow: logging shouldn't wait on anything that can fail for unrelated reasons, and a branch that sends emails isn't tested until I've read the emails.
Top comments (0)