FS-DOC-05Technical
Integration and API Spec
Email Newsletter Production
[YourCompany.com] · Marketing Department · Prepared by FullSpec · [Today's Date]
This document defines every API connection, authentication requirement, field mapping, and failure behaviour for the Email Newsletter Production automation. It is written for the FullSpec build team and covers all six tools in the confirmed stack: Mailchimp, Google Docs, Notion, Slack, Google Analytics, and the orchestration layer. Each section provides the exact configuration needed to connect, authenticate, and operate each integration reliably at the current production volume of approximately four newsletter sends per month.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agent
Mailchimp
Campaign creation, variable injection, scheduling, and send
API key (server-side)
Essentials ($13/mo min for API access)
Agent 1 (Content Assembly), Agent 2 (Reporting)
Google Docs
Contributor copy source: automation reads document content via API
OAuth 2.0 (service account)
Google Workspace free tier sufficient
Agent 1 (Content Assembly)
Notion
Content brief board and post-send performance card store
Notion Integration Token (internal)
Free tier sufficient; Plus recommended for API rate headroom
Agent 1 (Content Assembly), Agent 2 (Reporting)
Slack
Contributor brief delivery, broken-link alerts, and approval prompts
OAuth 2.0 (Bot Token via Slack App)
Free tier sufficient for message volume
Agent 1 (Content Assembly)
Google Analytics
Post-send session and click data for performance reporting
OAuth 2.0 (service account, GA4 Data API)
Google Analytics 4 free
Agent 2 (Reporting)
Automation platform
Orchestration layer: schedules triggers, routes data between tools, manages retries and credential store
Internal credential store (no external auth)
Paid tier required for webhook listeners and scheduled workflows
All agents
Before you connect anything: sandbox-test every connection using non-production credentials before any production API keys or live tokens are entered. Create a Mailchimp test account, a throwaway Notion integration, and a Slack test workspace. Confirm each tool responds correctly to read and write operations in isolation before wiring agents together.
02Per-tool integration detail
Mailchimp
Used by both agents. Agent 1 creates and populates campaign drafts via variable injection. Agent 2 reads campaign send metrics for reporting. The account must use a template built with Mailchimp merge tags or a coded template supporting variable substitution. Legacy hardcoded HTML templates are not compatible and must be rebuilt before go-live.
Auth method
API key authentication. The key is generated in Mailchimp under Account > Extras > API keys. Store as MAILCHIMP_API_KEY in the credential store. Never hardcode.
Required scopes
Mailchimp API keys are not scope-limited by default; they carry full account access. Restrict access by using a dedicated sub-account or by generating the key under a restricted Mailchimp user role with: campaigns:read, campaigns:write, campaigns:send, lists:read, reports:read, templates:read
Webhook / trigger setup
Agent 2 does not use a Mailchimp webhook. It polls the campaign endpoint 48 hours after a send timestamp is recorded internally. If near-real-time send detection is needed in future, configure a Mailchimp webhook at Account > Extras > Webhooks pointing to the platform's inbound webhook URL, subscribing to the campaign_sent event.
Required configuration
Store MAILCHIMP_LIST_ID (the target subscriber list), MAILCHIMP_TEMPLATE_ID (the approved send template), and MAILCHIMP_SERVER_PREFIX (e.g. us21) in the credential store. The template must define merge tags for each copy section: *|HEADLINE|*, *|INTRO_COPY|*, *|SECTION_1_BODY|*, *|SECTION_2_BODY|*, *|CTA_URL|*, *|CTA_LABEL|*, *|IMAGE_1_URL|*, *|IMAGE_2_URL|*. Do not hardcode these IDs in workflow steps.
Rate limits
Mailchimp Marketing API: 10 concurrent connections, no documented per-minute request cap for standard operations. Campaign create and send calls are low-frequency (one per newsletter cycle). At four sends per month, throttling is not required. Add a 2-second delay between sequential API calls as a conservative guard.
Constraints
The campaign must remain in draft status until the coordinator approves in Slack. Do not call the schedule or send endpoint before the approval flag is set. Sending to a test segment during QA requires a separate list ID stored as MAILCHIMP_TEST_LIST_ID.
// Agent 1 Input
formatted_copy_payload: { headline, intro, section_1, section_2, cta_url, cta_label, image_1_url, image_2_url }
// Agent 1 Output
campaign_id: string // Mailchimp draft campaign ID for downstream link check and approval
campaign_web_id: string // Used to build preview URL for Slack approval message
// Agent 2 Input
campaign_id: string // ID of the sent campaign
// Agent 2 Output
open_rate: float, click_rate: float, top_links: array[{url, clicks}], bounce_flag: booleanGoogle Docs
Read-only integration. Agent 1 retrieves contributor copy from a designated Google Doc per newsletter section. Each contributor's Doc is linked from their Notion task. The automation reads the full document body and extracts text by heading structure.
Auth method
OAuth 2.0 using a Google service account. Download the service account JSON key from Google Cloud Console, share each contributor Doc with the service account email address (read-only), and store the JSON key as GOOGLE_SERVICE_ACCOUNT_JSON in the credential store.
Required scopes
https://www.googleapis.com/auth/documents.readonly, https://www.googleapis.com/auth/drive.readonly
Webhook / trigger setup
No webhook. Agent 1 is triggered when a Notion task is marked complete (see Notion block). The automation then polls the linked Google Doc ID from the Notion task property. Push notifications via the Google Drive API Changes endpoint are optional for faster response but not required at this volume.
Required configuration
Each Notion contributor task must contain a Google Doc URL property (field: google_doc_url). The automation extracts the document ID from the URL pattern docs.google.com/document/d/{DOC_ID}/. No Doc IDs are hardcoded. The expected heading structure within each Doc must be documented and agreed with contributors: H1 = section title, H2 = subheadings, body text = copy, bold = CTA label.
Rate limits
Google Docs API: 300 read requests per minute per project. At four newsletter cycles per month with two to four contributor Docs per cycle, peak usage is under 20 requests per trigger event. Throttling is not required.
Constraints
The service account must be granted Viewer access to each Doc individually, or all Docs must reside in a shared Google Drive folder where the service account has folder-level read access. Docs that are not shared will return a 403 and must trigger a Slack alert to the coordinator.
// Input
document_id: string // Extracted from google_doc_url on the Notion task
// Output
document_body: { title: string, sections: [{ heading: string, body: string }] }
// Validation flags appended by Content Assembly Agent
section_length_ok: boolean, cta_present: boolean, missing_fields: array[string]Notion
Used by both agents. Agent 1 reads the content brief board to detect when contributor tasks are marked complete and retrieves linked Google Doc URLs. Agent 2 writes a structured performance summary card to the reporting database after each send.
Auth method
Notion Internal Integration Token. Create a new integration at notion.so/my-integrations, share the content brief database and the performance reporting database with the integration, and store the token as NOTION_INTEGRATION_TOKEN in the credential store.
Required scopes
read_content, update_content, insert_content (set when creating the internal integration). The integration must be explicitly connected to both the content brief database and the performance reporting database within Notion.
Webhook / trigger setup
Notion does not provide native outbound webhooks. Agent 1 polls the content brief database on a 10-minute interval using the database query endpoint, filtering for tasks where status equals Done and processed equals false. After reading a task, the automation sets processed to true to prevent double-processing.
Required configuration
Content brief database must contain properties: contributor_name (text), section_id (select), google_doc_url (url), status (select: Assigned, In Progress, Done), deadline (date), processed (checkbox). Performance reporting database must contain: campaign_id (text), send_date (date), open_rate (number, percent), click_rate (number, percent), top_link_1 through top_link_3 (url), top_link_1_clicks through top_link_3_clicks (number), bounce_flag (checkbox), notes (rich text). Store NOTION_BRIEF_DB_ID and NOTION_REPORTING_DB_ID in the credential store.
Rate limits
Notion API: 3 requests per second average, burst allowed. At 10-minute polling intervals and four newsletter cycles per month, steady-state usage is well under limits. No throttling required. If polling is tightened to 1-minute intervals during active production windows, add a 400ms delay between sequential database queries.
Constraints
The integration must be manually connected to each database in Notion by a workspace admin. This step is not automatable. Confirm during onboarding. Rich text blocks in performance cards must not exceed 2,000 characters per block (Notion API limit).
// Agent 1 Input (poll query)
filter: { property: 'status', select: { equals: 'Done' } }, AND { property: 'processed', checkbox: { equals: false } }
// Agent 1 Output (per task row)
contributor_name: string, section_id: string, google_doc_url: string, deadline: date
// Agent 2 Output (performance card write)
campaign_id, send_date, open_rate, click_rate, top_link_1..3, bounce_flag, notesIntegration and API SpecPage 1 of 3
FS-DOC-05Technical
Slack
Used by Agent 1 for three distinct message types: contributor brief delivery at the start of each cycle, broken-link alert to the coordinator when the link check fails, and approval prompt with a Mailchimp preview link after the draft is built and validated.
Auth method
OAuth 2.0 Bot Token. Create a Slack App in the target workspace, install it with Bot Token Scopes, and store the bot token as SLACK_BOT_TOKEN. Store the coordinator's Slack user ID as SLACK_COORDINATOR_USER_ID and each contributor's Slack user ID in their Notion contributor record.
Required scopes
chat:write, chat:write.public, im:write, users:read, users:read.email, channels:read
Webhook / trigger setup
Approval response: the approval prompt message uses Slack Block Kit with two action buttons (Approve Send, Request Edits). To capture the coordinator's click, the Slack App must have an Interactivity Request URL configured pointing to the automation platform's inbound webhook endpoint. This endpoint must validate the Slack request signature using the signing secret stored as SLACK_SIGNING_SECRET. Signature validation: compute HMAC-SHA256 of the raw request body using SLACK_SIGNING_SECRET and compare against the X-Slack-Signature header. Reject any request where the timestamp in X-Slack-Request-Timestamp is more than 5 minutes old.
Required configuration
Store SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET, SLACK_COORDINATOR_USER_ID, and SLACK_ALERT_CHANNEL_ID (for broken-link alerts) in the credential store. Contributor Slack user IDs are stored in Notion contributor records, not hardcoded. The approval window duration (default 4 hours) is stored as a configuration variable APPROVAL_TIMEOUT_HOURS. Do not hardcode.
Rate limits
Slack Web API: 1 message per second per channel for chat.postMessage (Tier 3, ~50 requests per minute burst). At four newsletter cycles per month with three to five messages per cycle, total monthly Slack API calls are under 25. Throttling is not required.
Constraints
The Slack App must be installed to the workspace by a workspace admin before any messages can be sent. DMs to contributors require im:write scope and the bot must be in a shared DM or the user must have accepted the DM. If a contributor's Slack user ID is missing from Notion, the brief must fall back to posting in the configured SLACK_ALERT_CHANNEL_ID with a manual follow-up flag.
// Input: contributor brief message
user_id: string, section_id: string, deadline: date, word_count_target: integer
// Input: approval prompt message
coordinator_user_id: string, campaign_web_id: string, preview_url: string, link_check_summary: object
// Output: approval response (via interactivity webhook)
action_id: 'approve_send' | 'request_edits', user_id: string, response_timestamp: unix_ts
Google Analytics
Read-only integration used exclusively by Agent 2. Retrieves session and click data from the GA4 property for the 48-hour window following each newsletter send. Campaign traffic is identified via UTM parameters appended to all newsletter links.
Auth method
OAuth 2.0 using a Google service account with GA4 Data API access. The same service account used for Google Docs can be reused if granted Viewer access to the GA4 property. Store the JSON key as GOOGLE_SERVICE_ACCOUNT_JSON (shared with Google Docs integration). Store the GA4 property ID as GA4_PROPERTY_ID in the credential store.
Required scopes
https://www.googleapis.com/auth/analytics.readonly
Webhook / trigger setup
No webhook. Agent 2 is triggered by an internal timer set 48 hours after the campaign send timestamp is recorded. The automation then calls the GA4 Data API runReport endpoint.
Required configuration
All newsletter CTA links must include UTM parameters: utm_source=newsletter, utm_medium=email, utm_campaign={campaign_id}. The campaign_id value must match the Mailchimp campaign ID to allow cross-referencing. Store GA4_PROPERTY_ID in the credential store. Do not hardcode. Report date range: startDate = send date, endDate = send date + 2 days.
Rate limits
GA4 Data API: 10 concurrent requests, 10,000 tokens per project per day (standard quota). A single runReport call for one campaign uses approximately 10-20 tokens. At four sends per month, monthly token usage is under 100. No throttling required.
Constraints
GA4 data can have a processing lag of 24 to 48 hours for some metrics. The 48-hour post-send trigger is chosen to allow data to settle. If the report returns incomplete data (sessionCount under 10), the Notion card must be written with a data_lag_warning flag set to true and re-queried at 72 hours.
// Input
property_id: string // GA4_PROPERTY_ID
date_range: { startDate: 'send_date', endDate: 'send_date+2' }
dimensions: ['pagePath', 'sessionSource', 'sessionMedium']
metrics: ['sessions', 'screenPageViews', 'bounceRate']
dimension_filter: { utm_source equals 'newsletter', utm_campaign equals campaign_id }
// Output
top_pages: array[{ path: string, sessions: integer }]
total_sessions: integer, bounce_rate: float03Field mappings between tools
The tables below document the exact field-level mappings for each agent handoff in the automation. Field names are shown in monospace as they appear in the relevant API response or payload. These mappings must be implemented exactly in the orchestration layer data transformation steps.
Handoff 1: Notion content brief database to Agent 1 (Content Assembly Agent)
Source tool
Source field
Destination tool
Destination field
Notion
properties.contributor_name.title[0].plain_text
Agent 1 internal
contributor_name
Notion
properties.section_id.select.name
Agent 1 internal
section_id
Notion
properties.google_doc_url.url
Google Docs API
document_id
Notion
properties.deadline.date.start
Agent 1 internal
deadline_iso
Notion
properties.processed.checkbox
Agent 1 internal
already_processed_flag
Handoff 2: Google Docs document body to Agent 1 content validation and Mailchimp template variables
Source tool
Source field
Destination tool
Destination field
Google Docs
body.content[heading1].paragraph.elements[0].textRun.content
Mailchimp
merge_fields.*|HEADLINE|*
Google Docs
body.content[heading2].paragraph.elements[0].textRun.content
Mailchimp
merge_fields.*|SECTION_1_BODY|*
Google Docs
body.content[paragraph][0].textRun.content
Mailchimp
merge_fields.*|INTRO_COPY|*
Google Docs
body.content[paragraph][-1].textRun.content
Mailchimp
merge_fields.*|SECTION_2_BODY|*
Google Docs
body.content[bold_run].textRun.content
Mailchimp
merge_fields.*|CTA_LABEL|*
Google Docs
body.content[hyperlink].textRun.textStyle.link.url
Mailchimp
merge_fields.*|CTA_URL|*
Notion task property
properties.image_1_url.url
Mailchimp
merge_fields.*|IMAGE_1_URL|*
Notion task property
properties.image_2_url.url
Mailchimp
merge_fields.*|IMAGE_2_URL|*
Handoff 3: Mailchimp campaign metrics and Google Analytics report to Agent 2 (Reporting Agent) to Notion performance card
Source tool
Source field
Destination tool
Destination field
Mailchimp Reports API
report.open_rate
Notion
properties.open_rate.number
Mailchimp Reports API
report.click_rate
Notion
properties.click_rate.number
Mailchimp Reports API
report.bounces.hard_bounces + soft_bounces
Notion
properties.bounce_flag.checkbox
Mailchimp Reports API
report.campaign_title
Notion
properties.campaign_id.title
Mailchimp Reports API
report.send_time
Notion
properties.send_date.date.start
Mailchimp Click Detail API
urls_clicked[0].url
Notion
properties.top_link_1.url
Mailchimp Click Detail API
urls_clicked[0].total_clicks
Notion
properties.top_link_1_clicks.number
Mailchimp Click Detail API
urls_clicked[1].url
Notion
properties.top_link_2.url
Mailchimp Click Detail API
urls_clicked[2].url
Notion
properties.top_link_3.url
Google Analytics
rows[0..2].dimensionValues[0].value (pagePath)
Notion
properties.notes.rich_text (appended)
Google Analytics
rows[*].metricValues[0].value (sessions)
Notion
properties.notes.rich_text (appended)
Integration and API SpecPage 2 of 3
FS-DOC-05Technical
04Build stack and orchestration
Orchestration layout
Two discrete workflows, one per agent, running independently on the automation platform. Workflow 1 (Content Assembly Agent) is schedule-triggered weekly and continues through contributor monitoring, content assembly, link checking, and the Slack approval gate. Workflow 2 (Reporting Agent) is triggered by an internal timer event set at campaign send time plus 48 hours. Both workflows share a single credential store. No data is passed between workflows directly: Mailchimp campaign_id is the shared key written by Workflow 1 and read by Workflow 2 via the platform's internal key-value store.
Workflow 1 trigger mechanism
Scheduled trigger: fires every Monday at 07:00 in the configured timezone (store as SCHEDULE_TIMEZONE). No webhook required for the start trigger. Mid-workflow, Notion task completion is detected by a polling sub-loop running every 10 minutes until all contributor tasks are marked Done or the copy deadline timestamp is reached. Slack approval response is received via webhook: the platform's inbound webhook URL is registered as the Slack App interactivity endpoint. Signature validation required on every inbound payload (see Slack block in section 02).
Workflow 2 trigger mechanism
Internal timer trigger: when Workflow 1 calls the Mailchimp schedule-send API step and records the campaign send time, it writes the value send_scheduled_at to the platform key-value store. Workflow 2 polls the key-value store every 15 minutes and fires when current time is greater than or equal to send_scheduled_at plus REPORTING_DELAY_HOURS (default 48, stored as a configuration variable). No external webhook is used.
Shared key-value store keys
campaign_id (string), send_scheduled_at (ISO 8601 timestamp), approval_status ('pending' | 'approved' | 'edits_requested'), link_check_passed (boolean), cycle_active (boolean). These keys are scoped per newsletter cycle and reset at the start of each Workflow 1 run.
Environment variables
SCHEDULE_TIMEZONE, APPROVAL_TIMEOUT_HOURS (default 4), REPORTING_DELAY_HOURS (default 48), MAX_RETRY_ATTEMPTS (default 3), BOUNCE_RATE_LOOKBACK_WEEKS (default 4). Stored as platform environment variables, not in the credential store.
Credential store contents (all secrets stored in the platform's encrypted credential store, never in workflow step configuration or version control):
Credential store: all values injected at runtime, never logged or exposed in step outputs
# Mailchimp
MAILCHIMP_API_KEY=<generated in Mailchimp account settings>
MAILCHIMP_SERVER_PREFIX=<e.g. us21>
MAILCHIMP_LIST_ID=<production subscriber list ID>
MAILCHIMP_TEST_LIST_ID=<QA test list ID>
MAILCHIMP_TEMPLATE_ID=<approved campaign template ID>
# Google (shared service account for Docs and Analytics)
GOOGLE_SERVICE_ACCOUNT_JSON=<full JSON key file content, base64-encoded>
GA4_PROPERTY_ID=<GA4 numeric property ID>
# Notion
NOTION_INTEGRATION_TOKEN=<internal integration secret>
NOTION_BRIEF_DB_ID=<content brief database ID from Notion URL>
NOTION_REPORTING_DB_ID=<performance reporting database ID from Notion URL>
# Slack
SLACK_BOT_TOKEN=<xoxb- prefixed bot token>
SLACK_SIGNING_SECRET=<from Slack App Basic Information page>
SLACK_COORDINATOR_USER_ID=<Slack user ID of the newsletter coordinator>
SLACK_ALERT_CHANNEL_ID=<channel ID for broken-link and error alerts>
# Platform internal
INBOUND_WEBHOOK_SECRET=<used to authenticate internal timer callbacks if applicable>
Rotate MAILCHIMP_API_KEY and SLACK_BOT_TOKEN at least every 90 days. Revoke and replace GOOGLE_SERVICE_ACCOUNT_JSON if any team member with access to the cloud project leaves. All credential rotation must be followed immediately by a connection test on each affected workflow step before the next production cycle runs.
05Error handling and retry logic
Every integration point has a defined failure behaviour. Unhandled exceptions must never fail silently. All errors must be logged to the platform's execution log and, where the failure affects a live newsletter cycle, a Slack alert must be posted to SLACK_ALERT_CHANNEL_ID with the error type, the affected step, and the recommended manual action.
Integration
Scenario
Required behaviour
Notion (poll)
Database query returns 401 Unauthorized
Halt workflow. Post Slack alert: 'Notion integration token invalid or database access revoked. Manual credential check required.' Do not retry automatically. Require human intervention.
Notion (poll)
No contributor tasks reach Done status before the copy deadline
After deadline timestamp passes, proceed with available copy and post Slack alert listing missing section IDs. Do not block the build step. Flag incomplete sections in the Mailchimp draft as placeholder text.
Google Docs
Document returns 403 Forbidden (not shared with service account)
Skip that contributor's section. Post Slack alert naming the contributor and Doc URL. Set missing_fields flag for that section. Retry once after 10 minutes in case of a transient permission propagation delay.
Google Docs
Document body is empty or heading structure does not match expected schema
Flag section as invalid. Post Slack alert to coordinator with the Doc URL and the validation error. Do not inject empty or malformed content into Mailchimp. Await manual correction and re-trigger.
Mailchimp (campaign create)
API returns 429 Too Many Requests
Retry with exponential backoff: wait 5 seconds, then 15 seconds, then 45 seconds. After 3 failed attempts, halt and post Slack alert. Log full response body.
Mailchimp (campaign create)
API returns 500 or 503 Server Error
Retry with exponential backoff: 10 seconds, 30 seconds, 90 seconds. After 3 attempts, halt workflow and post Slack alert: 'Mailchimp API unavailable. Newsletter build paused. Check status.mailchimp.com.' Do not proceed to link check.
Link check step
One or more URLs return non-200 status or connection timeout
Collect all failed URLs into a broken_links array. Post Slack alert to coordinator with the list of broken URLs and their HTTP status codes. Do not send the approval prompt until the coordinator confirms fixes are made. Resume from link check step only, not from full rebuild.
Slack (approval prompt)
Coordinator does not respond within APPROVAL_TIMEOUT_HOURS
Do NOT proceed with send. Post an escalation message to SLACK_ALERT_CHANNEL_ID tagging the coordinator. Wait one additional hour. If still no response, halt the send and log the cycle as timed_out. Manual rescheduling required.
Slack (interactivity webhook)
Inbound payload fails signature validation
Reject the request with HTTP 401. Log the raw payload and source IP. Do not update approval_status. Post a Slack alert to SLACK_ALERT_CHANNEL_ID: 'Suspicious approval payload rejected. Manual review required.'
Mailchimp (schedule send)
Coordinator approves but schedule API call fails
Retry twice with 30-second backoff. If both retries fail, post Slack alert to coordinator with the Mailchimp draft URL and instruct manual scheduling. Log the failure with the Mailchimp error response code.
Google Analytics (report query)
API returns incomplete data (total sessions under 10) at 48-hour mark
Write partial Notion card with data_lag_warning flag set to true. Schedule a re-query at send time plus 72 hours. Overwrite the Notion card with updated figures if the 72-hour query returns complete data.
Notion (performance card write)
API returns 400 Bad Request (field value exceeds limit or type mismatch)
Log the full Notion API error response. Truncate rich text fields to 2,000 characters and retry once. If retry fails, post Slack alert with the raw metrics payload so the coordinator can enter the data manually.
Global rule: no workflow step may swallow an exception without writing to the execution log and, for any failure that affects a live newsletter cycle, posting a Slack alert to SLACK_ALERT_CHANNEL_ID. Silent failures are treated as critical defects during QA. Confirm this behaviour is enforced at the platform level, not only within individual step error handlers.
For build queries or integration questions, contact the FullSpec team at support@gofullspec.com.
Integration and API SpecPage 3 of 3