How to Automate New-Hire Announcements with an AI Company Brain

Automate the drafting of a new-hire announcement, not the decision to send it. Let the assistant prepare the message from a short list of approved fields, send the team note early and the company note on the start date, and publish only through a narrow adapter that checks the hire's state and the channel's audience immediately before the send. We recommend this because a sent message is harder to correct than a draft, and in Slack a bot that fixes its own message does it with no visible trace.
What the output looks like
Team note, sent early to the people who need lead time: "Sam Lee joins Customer Success on Monday as a Customer Success Manager, reporting to [manager]. [Buddy] is Sam's onboarding buddy, and the first-week plan is on the team page: [link]. Say hello in this thread."
Company note, sent when the start date has passed without a change: "Please welcome Sam Lee, who joins Customer Success as a Customer Success Manager on Monday. Sam previously worked on customer education programs. Say hello in this thread."
Both come from one approved record of name, role, team, start date and, if the employee chose to give it, one line of background.
Who owns each decision
A welcome message looks like one task and is really six states with different owners. The HR system owns whether the person is joining and when. The employee owns which optional details may be shared. The hiring manager owns role and team context. People Operations or internal communications owns audience and tone. Slack, email or another channel owns delivery state. A named operator owns corrections. An assistant that posts straight from a recruiting profile collapses all six into one model response, so the text can be fluent while the decision is wrong.
The Indeed new employee announcement guide lists what a company-wide announcement might hold: name, start date, title, department, supervisor and key responsibilities, then academic and professional background, two or three fun facts and contact details. Everything after the first six is the employee's to give, so it belongs on the optional list, field by field. Indeed itself advises asking permission before publishing a photo or personal details. GitLab's onboarding handbook shows the destination discipline: new team members are added from day one to a fixed list of Slack channels, one of which, #team-member-updates, is for new team members, anniversaries and goodbyes, so announcements have a known home and not a fresh choice for every message.
What the assistant may and may not do
Write the boundary before connecting the HR system to a messaging channel. The assistant may retrieve an approved template, read a minimal set of current joiner fields, draft team and company variants, flag missing approvals or conflicting values, and call an approved delivery adapter once every gate passes. It must not copy interview notes, compensation, background checks, accommodation details or personal contact data into a message, infer hobbies, pronouns or family details from unrelated records, treat a calendar event as proof that someone is starting, choose a company-wide audience because a template uses broad language, report delivery because an API accepted a request, or edit or delete a sent message without an accountable operator.
NIST frames its Privacy Framework as a voluntary tool for building products and services while protecting individuals' privacy. An announcement is a small service that publishes facts about a person, so apply the same trade-off: collect only the data the announcement needs, document the purpose, control who can approve and send it, and keep only the operational evidence needed to correct a mistake.
The announcement record
Do not send raw HR records to a model. Create a small record with typed fields and provenance:
announcement_id: ann_2026_0142
employee_id: emp_1042
joiner_state: confirmed
start_date: 2026-09-14
state_checked_at: 2026-09-13T16:00:00Z
team_id: customer_success
manager_id: mgr_87
role_title: Customer Success Manager
approved_name: Sam Lee
approved_pronouns: null
approved_bio: "Previously worked on customer education programs."
optional_fields_approved_at: 2026-09-10T09:20:00Z
audiences:
- team_customer_success
- company_general
approver_ids:
- mgr_87
- people_ops_12
workflow_state: awaiting_approval
template_version: welcome_v4joiner_state and start_date come from the authoritative employee lifecycle system. approved_name, approved_bio and the other optional details come from a step the employee sees. The manager may propose role context but must not approve optional personal facts on the employee's behalf. Use an allowlist and not a blocklist, because a blocklist has to predict every sensitive field a recruiting record might hold:
required_fields = [approved_name, role_title, team_id, start_date]
optional_fields = [approved_pronouns, approved_bio, approved_photo]
forbidden_sources = [interview_notes, compensation, background_check, medical_or_leave_records]If a required field is missing or conflicts with another system, stop the workflow, and never ask the model to pick the value that looks most plausible.
Two audiences, two timings
A team introduction and a company-wide announcement do different jobs, so generate them from the same record with separate templates and separate approvals. The team note carries practical first-week context: the role and immediate team, the confirmed start date, who will help with onboarding, how teammates should introduce themselves and links to approved team knowledge. The company note is narrower: approved name, role and team, a start date or a general timing phrase, the employee-approved biography and a simple welcome action. Leave out home address, personal email, phone number, exact working schedule, unapproved location and the detailed first-week calendar. The announcement helps colleagues welcome the employee and should not expose the onboarding plan.
We recommend splitting the timing as well as the audience. Indeed's guide suggests sending the announcement a week or two before the start date so colleagues can adjust their work, and giving extra notice to the people who supervise or assist the new hire. That reasoning covers the team, the manager and the buddy. It does not cover the rest of the company, which needs the news only when the person is there. Send the team note early, and hold the company note until the start date has passed without a change, because a team note is cheap to retract and a company-wide one is not.
Store each rendered text with its template_version and a hash of the approved input. If the input changes, the approval is void, so a start-date correction or a biography edit cannot slip into a message approved earlier.
Consent and approval are separate gates
The employee approves optional personal content and sees the exact rendered sentence, not a vague checkbox allowing the company to share "profile information". They can remove optional details without affecting employment or required onboarding. The hiring manager confirms role, team and practical context, and People Operations or internal communications confirms audience, template, timing and policy fit. In a small team one person may hold both business roles, but the workflow still records which decision they made. Use a state machine and not several unrelated checkboxes:
collecting
-> awaiting_employee_consent
-> awaiting_business_approval
-> scheduled
-> sending
-> sent_unverified
-> verified
any pre-send state -> cancelled
sent_unverified or verified -> correction_requiredEach transition needs an actor, a timestamp and a reason. Approval binds to the input hash, audience, channel and scheduled time, and a change to any of them returns the record to the right approval state.
Publish through a narrow adapter
Give the assistant one constrained operation such as publish_approved_announcement(announcement_id), and no general Slack token or mail credential to build arbitrary requests with. The adapter resolves an approved audience identifier to an allowlisted channel, and the model never invents a channel ID. In Slack, the chat.postMessage method returns the channel and the message timestamp on success. Store both with the send record, because they are the only handle you will have for a later correction.
Allowlisting a channel by ID is not enough, because who is in it can change. Before each send, read the channel with conversations.info (it needs channels:read for public channels) and refuse to post a personal detail if the sharing fields, is_ext_shared, is_org_shared or is_pending_ext_shared, show that the channel is shared outside the workspace or has an invitation pending, unless the template was written for that audience. A channel that was internal when you allowlisted it may since have been connected to another organization.
For email, the Microsoft Graph sendMail API returns 202 Accepted with no response body, and the page says that code means the request was accepted and not that processing has completed or the message was delivered. There is no message ID to record. The method saves a copy in Sent Items unless saveToSentItems is false, and the page offers creating a draft to send later as the alternative. Keep the workflow in sent_unverified until one of those confirms the send.
Make sends idempotent, and check the joiner state and the channel immediately before the irreversible action and not only when the draft was created:
idempotency_key = announcement_id + audience_id + template_version
if verified_send_exists(idempotency_key):
return existing_result
if joiner_state != "confirmed":
return cancel("Joiner state changed")
if approvals_do_not_match_current_input():
return block("Approval is stale")
if channel_is_shared_outside_audience(approved_destination):
return block("Channel audience changed")
result = delivery_adapter.send(rendered_message, approved_destination)
record_attempt(idempotency_key, result)That also protects against duplicate posts when a worker retries after a timeout.
Changes before and after the send
Start dates move, people change teams and some hires do not start. Before sending, subscribe to authoritative joiner-state and start-date changes, cancel scheduled messages when the state is no longer confirmed, invalidate approvals when the name, role, team, biography, audience or send time changes, tell the owner which field changed, and require a fresh employee review when optional content changed.
After sending, in Slack, correct a wrong detail with a visible reply in the thread and not with an edit. Slack documents that the (edited) label renders only on messages edited by humans and that a message with a bot_id never shows it (chat.update), so a bot that fixes its own announcement changes the text with no trace for the people who already read it. A bot token can update or delete only the messages that bot posted, so the correction has to run through the same app with the stored channel and timestamp.
Reserve deletion for an accidental sensitive disclosure, and remember that it does not prove that copies, inboxes, notifications or exports disappeared. Record the correction decision, the channel, the operator and the final state. If the employee does not start, stop future messages and follow the broader onboarding rollback process, without posting a second company-wide message about private hiring circumstances.
Give each failure a safe response
| Failure | Workflow response | Owner action |
|---|---|---|
| Joiner state is not confirmed | Cancel the scheduled send. | People Operations resolves the employee record. |
| Start date changed | Invalidate timing approval. | Manager and employee review the new draft. |
| Optional detail lacks consent | Remove it from every audience draft. | Employee may approve a revised value. |
| Audience cannot be resolved | Block delivery. | Internal communications selects an allowlisted destination. |
| Slack or mail request fails | Keep the record unsent and preserve the provider error. | Channel owner repairs configuration before retrying. |
| Request accepted but not verified | Keep sent_unverified. | Operator checks channel state without issuing a duplicate send. |
| Wrong detail was published | Open correction_required. | Accountable owner edits, corrects, or retracts using the channel policy. |
| Sensitive detail was exposed | Stop automation and restrict access to the incident record. | Privacy or security owner follows the approved incident process. |
A failure message says what happened, what did not happen and who owns the next action, and it never pastes the sensitive field into logs or notifications.
Test it with synthetic employees
Use synthetic employees before real joiner data, with normal sends and negative cases. The suite should prove that only allowlisted fields reach the rendered message, that missing consent removes optional details, that a changed start date voids the approval, that a withdrawn joiner cannot be sent, that a retry returns the first send result and a provider timeout does not trigger a duplicate, that delivery stays unverified until confirmed, that a channel which becomes shared blocks the send, and that logs hold identifiers and state and not recruiting notes or biography drafts. Then run a tabletop exercise with People Operations, the manager and the channel owner on a scheduled message for a hire whose start date just moved: each should know who cancels it, where the source state changes, whether consent must be renewed and how the corrected message returns to the queue.
Kipwise employee onboarding provides the company-brain layer for assigned knowledge, searchable information, and onboarding progress. Use that layer to hold the approved template, owner instructions, and safe workflow status. Keep joiner state in the HR system and final delivery state in the messaging platform.
Start with one team note
Pick one team and one channel. Build the announcement record, the consent screen, the approval binding, the delivery adapter and the correction drill, and run it first with synthetic records and then with one controlled pilot. Add the company-wide note after that, and do not open every audience at once.



