Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ For information on how to run each project, see the README in each directory.
| [Python PDF form extractor](/python-pdf-form-extractor) | Extract data from PDF forms using Python ([PyMuPDF](https://pypi.org/project/PyMuPDF/)) and Trigger.dev |
| [Realtime CSV importer](/realtime-csv-importer) | Import and process CSV files with real-time progress updates using [UploadThing](https://uploadthing.com/) and Trigger.dev |
| [Realtime fal.ai image generation](/realtime-fal-ai-image-generation) | Generate images from text prompts using [Fal.ai](https://fal.ai/) with real-time progress display using [Trigger.dev Realtime](https://trigger.dev/docs/realtime/overview) |
| [Resend email agent with Slack approval](/resend-slack-email-agent) | An AI agent that drafts replies to a [Resend](https://resend.com) inbox and asks you in Slack before sending. Uses [hosted webhooks](https://trigger.dev/docs/webhooks) for Resend and Slack, and [wait tokens](https://trigger.dev/docs/wait-for-token) to pause each email thread until you click Send |
| [Remix webhooks](/remix-webhooks) | Example of triggering tasks from incoming webhooks in a [Remix app](https://remix.run/) with Trigger.dev |
| [Supabase edge functions](/supabase-edge-functions) | Learn how to trigger tasks from [Supabase Edge Functions](https://supabase.com/docs/guides/functions) and Database changes |
| [Vercel AI SDK image generator](/vercel-ai-sdk-image-generator) | Generate images using [OpenAI DALL-E](https://openai.com/dall-e) models with Vercel's [AI SDK](https://sdk.vercel.ai/docs/introduction) and Trigger.dev |
Expand Down
21 changes: 21 additions & 0 deletions resend-slack-email-agent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Your Trigger.dev project ref (from the project settings page in the dashboard)
TRIGGER_PROJECT_REF=proj_xxxxxxxxxxxxxxxxxxxxxxxx

# Resend API key with access to Inboxes (reads threads, sends replies)
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxx

# Slack app's Bot User OAuth Token
SLACK_BOT_TOKEN=xoxb-xxxxxxxxxxxxxxxxxxxxxxxx

# The one Slack user the bot DMs and listens to (Slack profile > ... > Copy member ID)
SLACK_USER_ID=U0123456789

# Vercel AI Gateway key, used by the AI SDK to draft replies
AI_GATEWAY_API_KEY=

# Only emails from these sender domains get drafted (comma-separated).
# Leave empty to draft a reply to every email.
ALLOWED_SENDER_DOMAINS=yourcompany.com

# Optional: a different AI Gateway model id
# INBOX_BOT_MODEL=anthropic/claude-opus-5.5
5 changes: 5 additions & 0 deletions resend-slack-email-agent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
.env
.env*
!.env.example
.trigger
37 changes: 37 additions & 0 deletions resend-slack-email-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Resend email agent with Slack approval

An AI agent that drafts replies to the email in a [Resend Inboxes](https://resend.com/docs/api-reference/inboxes) inbox and sends nothing until you approve it in Slack.

Every email chain becomes its own thread in your DMs with the bot. The agent drafts a reply and posts it with **Send** and **Don't send** buttons. To change a draft, reply in the thread ("shorter", "say we're closed over Christmas") and it writes a new one. Later emails in the same chain land in the same Slack thread, with a new draft.

## How it works

Each email chain gets one long-running [Trigger.dev](https://trigger.dev) run that sleeps on a [wait token](https://trigger.dev/docs/wait-for-token) between events. The token is tagged with the email thread id and the Slack message timestamp, so a webhook from either side can find it and wake the run. A waiting run holds no machine and no concurrency slot, so a draft can wait days for your click.

- `src/trigger/endpoints.ts`: the two hosted webhook endpoints, one for Resend and one for Slack. Trigger.dev checks each request's signature before your code runs.
- `src/trigger/email-thread.ts`: one run per email chain. It posts the email to Slack, drafts a reply, creates a wait token and waits on it. A click sends or drops the draft, a thread reply redrafts it, and a new email posts and redrafts. After 30 quiet days the run ends; the next email starts a new run in the same Slack thread.
- `src/trigger/inbound.ts`: the webhook tasks. A new email wakes the chain's run, or starts one. Slack thread replies and button clicks complete the waiting token.
- `src/trigger/draft.ts`: the drafting prompt, using the [AI SDK](https://ai-sdk.dev) through the Vercel AI Gateway. Tune `SYSTEM_PROMPT` for your inbox.
- `src/trigger/resend.ts` and `src/trigger/slack.ts`: thin API clients.

What keeps it safe:

- **A person sends every reply.** The model has no tools, so a hostile email can only change the words of a draft you read first.
- **Email text is escaped** before it goes into Slack, so an email can't @-mention anyone or disguise a link.
- **Only you can act.** The Slack app is DM-only, and clicks or replies from anyone other than `SLACK_USER_ID` are ignored.
- **Senders can be filtered.** `ALLOWED_SENDER_DOMAINS` stops unknown senders before they reach Slack or cost a model call.
- **Sends are idempotent.** A retried attempt reuses the same Resend idempotency key, so nobody gets a reply twice.

Hosted webhooks are in public beta. The packages are pinned to the `4.8.0-webhooks` beta release.

## Setup

You need a [Trigger.dev](https://cloud.trigger.dev) project, a Resend inbox on a verified domain, a Slack workspace where you can create apps, and a [Vercel AI Gateway](https://vercel.com/docs/ai-gateway) key.

1. Copy `.env.example` to `.env` and set `TRIGGER_PROJECT_REF`, `RESEND_API_KEY`, `SLACK_USER_ID`, `AI_GATEWAY_API_KEY` and `ALLOWED_SENDER_DOMAINS`.
2. Run `pnpm install` and `pnpm dev`. The `resend` and `slack` endpoints appear on your environment's **Webhooks** page with their URLs.
3. **Resend:** add a webhook pointing at the `resend` endpoint URL with the `inbox.email.received` event. Paste its signing secret into the `resend` endpoint in the Trigger.dev dashboard.
4. **Slack:** put the `slack` endpoint URL in both `request_url`s in `slack/manifest.yaml`, then create the app from it (**Create New App** > **From a manifest**). Paste the app's **Signing Secret** into the `slack` endpoint and click **Verify** in Slack's Event Subscriptions. Install the app and set `SLACK_BOT_TOKEN` (`xoxb-...`).
5. Restart `pnpm dev` and send an email to your inbox.

For production, run `pnpm run deploy`, set the same environment variables on the prod environment in the dashboard, and point Resend and Slack at the prod endpoint URLs.
28 changes: 28 additions & 0 deletions resend-slack-email-agent/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "resend-slack-email-agent",
"private": true,
"type": "module",
"scripts": {
"dev": "trigger dev",
"deploy": "trigger deploy",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/sdk": "4.8.0-webhooks.20261020092920",
"@trigger.dev/slack": "4.8.0-webhooks.20261020092920",
"ai": "^7.0.143",
"zod": "^4.6.7"
},
"devDependencies": {
"@trigger.dev/build": "4.8.0-webhooks.20261020092920",
"@types/node": "^26.6.5",
"trigger.dev": "4.8.0-webhooks.20261020092920",
"typescript": "^5.9.3"
},
"packageManager": "pnpm@10.34.5",
"pnpm": {
"onlyBuiltDependencies": [
"esbuild"
]
}
}
Loading