Slack-powered email client with pluggable infrastructure.
Receive emails and forward them to a Slack channel.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Email │────▶│ SES │────▶│ S3 │────▶│ Lambda │
│ Sender │ │ (receive) │ │ (storage) │ │ (process) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Slack │
│ (notify) │
└─────────────┘
- Node.js 24.x
- pnpm 10.x
- AWS Account with appropriate permissions
- Slack Workspace with admin access
- Domain with Route53 hosted zone (for email receiving)
- Pulumi Cloud account (for state management)
-
Create a new app at Slack API
-
Add the following Bot Token Scopes under OAuth & Permissions:
| Scope | Description |
|---|---|
chat:write |
Post messages to channels |
chat:write.public |
Post to public channels the bot isn't a member of |
files:write |
Upload, edit, and delete files (required for long emails >2800 characters) |
channels:history |
Read messages from public channels (for fetching email templates) |
groups:history |
Read messages from private channels (if using private channels) |
- Enable Event Subscriptions and subscribe to the following bot events:
| Event | Description |
|---|---|
app_mention |
Detect when the bot is mentioned (@yourbot) |
message.channels |
Listen for messages in public channels (optional) |
-
Install the app to your workspace under Install App
-
Note down the following credentials:
- Bot User OAuth Token (
xoxb-...) →SLACK_BOT_TOKEN - Signing Secret (under Basic Information) →
SLACK_SIGNING_SECRET - Channel ID (target channel for notifications) →
SLACK_CHANNEL_ID
- Bot User OAuth Token (
Note: You can get the Channel ID by right-clicking a channel in Slack → "Copy link" and extracting the last part of the URL (e.g.,
C01234ABCDE)
| Command | Description |
|---|---|
@yourbot template |
Generate an email template to fill out |
@yourbot <message_url> |
Parse a message and show email send confirmation |
Note: Replace
@yourbotwith your actual bot name.
- Mention the bot with
@yourbot templateto get a template - Copy the template, fill it out, and post as a new message
- Right-click the message → Copy link
- Mention the bot with
@yourbot <copied_url> - Review the preview and click "Send Email" to send
Required permissions for deployment:
s3:*(S3 bucket management)lambda:*(Lambda function management)iam:*(IAM role/policy management)ses:*(SES configuration)route53:*(DNS records)logs:*(CloudWatch Logs)apigateway:*(API Gateway, if used)
OIDC authentication is recommended for GitHub Actions deployments:
-
Create an Identity Provider in AWS IAM:
- Provider URL:
https://token.actions.githubusercontent.com - Audience:
sts.amazonaws.com
- Provider URL:
-
Create an IAM Role with the following Trust Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:YOUR_ORG/slackmail:*"
}
}
}
]
}- Create an account at Pulumi Cloud
- Generate an Access Token →
PULUMI_ACCESS_TOKEN - Create a stack (e.g.,
rindrics/slackmail/dev)
- Create a Hosted Zone for your domain (or use an existing one)
- Note down the Zone ID →
ROUTE53_ZONE_ID - Decide on the email receiving domain →
EMAIL_DOMAIN
# Pulumi
export PULUMI_ACCESS_TOKEN=pul-xxxx
# AWS (for local pulumi preview/up)
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_REGION=ap-northeast-1
# Application Config
export EMAIL_DOMAIN=example.com
export ROUTE53_ZONE_ID=Z1234567890ABC
# Slack (for Lambda)
export SLACK_SIGNING_SECRET=...
export SLACK_BOT_TOKEN=xoxb-...
export SLACK_CHANNEL_ID=C01234ABCDE| Name | Type | Description |
|---|---|---|
AWS_ROLE_ARN |
Secret | IAM Role ARN for OIDC |
PULUMI_ACCESS_TOKEN |
Secret | Pulumi Cloud Token |
ROUTE53_ZONE_ID |
Secret | Route53 Hosted Zone ID |
SLACK_SIGNING_SECRET |
Secret | Slack App Signing Secret |
SLACK_BOT_TOKEN |
Secret | Slack Bot OAuth Token |
EMAIL_DOMAIN |
Variable | Email receiving domain |
SLACK_CHANNEL_ID |
Variable | Target Slack Channel ID |
# Install dependencies
pnpm install
# Build all packages
pnpm run build
# Run tests
pnpm run test
# Lint and format
pnpm run check
pnpm run fix.
├── packages/
│ └── core/ # Core business logic (domain, application, infrastructure)
└── infra/
└── aws/ # AWS infrastructure (Pulumi) and Lambda handler
cd infra/aws
pnpm run previewcd infra/aws
pnpm run upcd infra/aws
pnpm run destroyNew AWS accounts have SES in sandbox mode. For production use:
- Go to AWS Console → SES → Account dashboard
- Click "Request production access"
- Fill in the use case and submit
- Verify the bot is a member of the target channel
- With
chat:write.publicscope, the bot can post to public channels without being a member - For private channels, invite the bot using
/invite @bot-name
- Verify MX records are correctly configured:
dig MX example.com
- Check that SES domain verification is complete
- Verify SES Receipt Rule is enabled
- Check Lambda CloudWatch Logs for errors
Emails exceeding Slack Block Kit character limits are handled automatically:
- Email subject > 140 characters: Truncated with "..." ellipsis
- Email body > 2800 characters: Uploaded as a
.txtfile with preview
Note: If you see a
missing_scopeerror for long emails, ensure thefiles:writescope is added to your Slack app (see Setup → Slack App Setup).
MIT