An event can have the right date on its website and the wrong one in the email somebody copied last week. Both messages look finished. The problem is that nobody can tell which copy the organiser changed most recently.

This build starts with one event record and uses it to prepare the information for several destinations. You still review what each channel will publish, but you stop retyping the same facts into unrelated drafts. The n8n example creates three draft jobs from an approved revision; it does not publish a post or send an email.

Write the event record before connecting a platform

Start with the fields that must agree everywhere: an internal event ID, a title, the exact start time when confirmed, the venue and the booking link. Add a revision number so a later change has an identity of its own. A spreadsheet row or JSON object is enough to work through this first version.

Keep missing information missing. A planned event can have a title and theme before it has a bookable date. It should not pass the same checks as a finished announcement. For Little Automations, the public page can explain the night and offer mailing-list updates while the exact date and venue details are still to come.

For testing, use an invented event called Example Build Night, an ID such as demo-night-001 and a clearly fictional room. Give it a timestamp with an explicit offset and a booking URL on the reserved .invalid domain. These sample details are for your workflow test; they must never replace the public AutomateNI event record.

Timezone information belongs with the time. A bare seven o'clock leaves the next system guessing. Preserve the original timezone or offset, then format it for the reader at the destination. Check daylight-saving behaviour before relying on a scheduled message for a real event.

Sources: Python: aware and naive date/time objects.

Approve a revision, not just an event name

Suppose the organiser approves revision two. You prepare an email from it, then the venue changes and the source becomes revision three. The old approval cannot sensibly cover the new address. Store which revision was approved and stop downstream work when it does not match the current one.

That is the purpose of approved_revision in the code below. It is a data check, not an authentication system. In a deployed workflow, only the intended reviewer should be able to set approval, and the record must be read from a trusted store. A public form that lets anyone submit approved: true is not an approval process.

Keep approval separate from the result of sending. An approved draft may still be waiting to publish, and a publishing request can fail after approval. If those states collapse into one green tick, somebody will eventually assume an announcement exists when it does not.

n8n's Wait node can help pause a workflow for a later step, but pausing alone does not decide who is allowed to approve. Check the resume route and authentication, keep the relevant revision with the request and make the review action explicit. For the first test, a manual reviewer updating a controlled record is easier to inspect.

Sources: n8n: Wait node.

Create a small set of draft jobs

In n8n, provide the event object to a Code node set to Run Once for All Items. This example reads the first incoming item, checks its required fields and approval revision, then returns separate website, email and social draft jobs. Keep the input to one event while learning how the output moves through the workflow.

Each job carries the same facts and a key made from event ID, revision and destination. A receiver can use that key to recognise a repeated request. Generating the key does not itself deduplicate anything: save it in durable storage with a uniqueness rule before allowing an external action.

The code leaves all three outputs in draft state. That is intentional. The next step can format a website description, a subject line or a short post without making the approval of those three pieces automatic. You should be able to inspect the output of the Code node before any publishing integration is connected.

// n8n Code node: Run Once for All Items.
const event = $input.first().json;
for (const field of ['id', 'title', 'starts_at', 'venue', 'booking_url']) {
  if (typeof event[field] !== 'string' || !event[field].trim()) {
    throw new Error(`Missing ${field}`);
  }
}
if (!Number.isInteger(event.revision) || event.revision < 1 ||
    event.approved !== true || event.approved_revision !== event.revision) {
  throw new Error('This revision needs approval');
}
if (!/(Z|[+-]\d{2}:\d{2})$/.test(event.starts_at) ||
    Number.isNaN(Date.parse(event.starts_at))) {
  throw new Error('Use a valid timestamp with its timezone offset');
}
if (!/^https:\/\/[^\s/?#@]+(?:[/?#]|$)/i.test(event.booking_url)) {
  throw new Error('Use an HTTPS booking link without credentials');
}
return ['website', 'email', 'social'].map(destination => ({
  json: {
    job_id: `${event.id}:${event.revision}:${destination}`,
    destination,
    state: 'draft',
    title: event.title,
    starts_at: event.starts_at,
    venue: event.venue,
    booking_url: event.booking_url
  }
}));

Sources: n8n: Code node.

Let each destination keep its own result

Attach a separate branch for each destination. Record whether it is waiting for review, approved, accepted by a provider, confirmed published or failed. Save the remote post ID or message reference when one is returned. Those details give you a way to check an uncertain outcome without creating another copy.

A website update succeeding does not mean the email succeeded too. Keep their receipts separate and retry only the branch that needs attention. If an API times out, look for the existing result using the job key or provider reference before sending another request. The provider may have completed the action even though your workflow did not receive the response.

For a first working version, you can replace every external branch with a file or a review table. The website job becomes draft text, the email job becomes a preview, and the social job becomes a line in the queue. This is enough to see whether the shared facts survive the hand-off.

Do not add attendee details to an event-publishing record just because another service makes them available. Public event facts and a private mailing list have different jobs. The workflow can prepare an announcement without copying contacts through every branch.

Sources: n8n: error handling.

Make a changed venue easy to handle

Try a deliberate change once the three drafts exist. Increment the revision, change the fictional venue and remove the old approval. The validator should stop until the new revision is approved. The replacement jobs should get new keys, while the old receipts remain available for comparison.

You also need a policy for material already published. Some destinations can edit an existing post; others need a correction. A reminder queued from the old revision should be cancelled or rebuilt before it goes out. Show the reviewer which outputs are stale instead of quietly preparing another full set.

Keep the public event page as the place people can check the current details. Link to it consistently from shorter messages. That does not excuse incorrect dates elsewhere, but it gives a person one sensible place to return when they are unsure which announcement is latest.

Test the workflow without announcing a fictional night

Run the same approved sample twice and compare the job IDs. They should match. A receiver test should then prove that the second set does not create duplicate actions. Change the revision without changing approved_revision and confirm that the code refuses it. Remove the booking URL or timezone offset and check the error message.

Next, make one destination fail while the others succeed. You should be able to identify and retry that one destination without restarting the entire announcement. Check that no stage describes a saved draft as a sent email or a published event. These distinctions are worth getting right before connecting any live audience.

When you show the build, put the event record beside the resulting drafts. Change one field and show what stops, what updates and what needs another review. That demonstration makes the value clear without needing a complicated diagram of every platform you might connect later.

Sources and further reading

Continue with