Try it: open https://dk88byqyuskma.cloudfront.net/ — the demo passphrase is in the README of the shared repository.
Inspiration
99.7% of companies in Japan are small and medium-sized businesses. Outside the company, email is still king: 98% of workers name it as a main channel and the average person gets about 50 emails a day. Inside the company, half of businesses now run on a chat tool such as Slack, Teams or Chatwork.
So most small companies live with two front doors. Customers write to one staff member and CC their boss "just in case", everyone assumes someone else will answer, and the one important request gets buried under sales mail. People work in chat all day but have to keep a mail client open on the side. Replies come late, and trust erodes.
We wanted the important customer email to simply show up in the right chat room, mention the right person, and be answerable right there.
What it does
Tsukinowa ("moon ring") is a message hub that each company runs in its own AWS account. Every messenger is a spoke, we are the hub. Like the moon, we don't make the light. We deliver it.
- A customer emails a staff member's own address. Amazon SES receives it and stores the raw MIME in S3.
- Amazon Bedrock (Claude) triages it: category, urgency, a three-line summary and a suggested action. Spam and sales mail are filtered out; look-alike domains and SPF/DKIM/DMARC failures are recorded but never notified.
- An AI dispatcher writes a delivery plan: who owns it, which Slack channel gets it, and what happens if nobody reacts. It follows plain-language routing instructions the company writes ("urgent mail with no reaction in 1 minute → SMS the owner", "scheduling requests never escalate", "outside working hours, also tell a colleague who is on duty"). Every plan shows which instructions it followed and why.
- The notification is posted to Slack (and to a browser chat for judges) with a mention of the owner.
- If nobody presses I'm on it, an EventBridge Scheduler step sends an SMS to the owner's phone through AWS End User Messaging SMS.
- The owner just writes the reply text in the thread. Tsukinowa shows a preview with "Re: subject" and their signature, then a final confirmation. Only after OK, send does SES send the reply from the person's own address, threaded under the original email (In-Reply-To / References).
People can hand a mail to a colleague, and the dispatcher takes that into account for the next mail of the same kind.
Safety by design: the AI only returns a classification and a plan in a fixed schema; it has no tools and cannot send anything. Plans are validated in code against the company directory. There is no code path that sends email without the owner's OK, and a test exhaustively tries every operation to prove it.
How we built it
- AWS CDS: Amazon SES for inbound and outbound mail (
@aws-sdk/client-sesv2, raw MIME so threading headers are exact) and AWS End User Messaging SMS for escalation (@aws-sdk/client-pinpoint-sms-voice-v2,SendTextMessage). - AI: Amazon Bedrock Converse API with
jp.anthropic.claude-sonnet-4-6, called twice per mail: triage, then dispatch. Email bodies go in as untrusted data, and outputs are validated with zod. - Serverless backbone: 7 AWS Lambda functions (TypeScript, Node.js 22), API Gateway (HTTP API) for Slack events and the demo API, SQS for chat commands (Slack needs an answer within 3 seconds), EventBridge Scheduler for delayed plan steps, DynamoDB single-table storage, S3, CloudFront for the demo UI, Route 53, Secrets Manager and SSM Parameter Store.
- Everything is one AWS CDK stack, so
cdk deployreproduces it in another account andcdk destroyremoves it. - The core of the hub is channel-neutral. Slack, SMS, WhatsApp and the browser demo are "spokes" behind one interface, so adding Teams or Chatwork means writing one more spoke.
- We built it during the hackathon with an AI coding assistant (Claude Code).
Challenges we ran into
- SES sandbox. We kept SES in the sandbox by putting both the staff side and the customer side (
abc.subdomain) under one delegated subdomain. Both are verified with DKIM, so judges can send and receive real mail without production access. - Threading. SES rewrites the Message-ID on raw sends. We had to confirm the
<[email protected]>format on real AWS, so that a customer's reply links back to the same conversation. - Real-AWS-only bugs. The DKIM CNAMEs for the customer subdomain were created with the zone name appended twice, so SES never verified it. A DynamoDB query sent an empty
begins_withprefix, which works in our in-memory store but is rejected by DynamoDB. Local tests passed in both cases; only the on-AWS run caught them. - SMS to Japan. Japanese long codes are not supported, so we use an alphanumeric sender ID (
TSUKINOWA) and verified destination numbers in the SMS sandbox. - Keeping the AI in its lane. Our dispatcher once planned an SMS to a colleague who was off duty. We added a hard rule in code: a backup colleague must be within working hours.
Accomplishments that we're proud of
The whole loop runs on real AWS, end to end: customer email → AI triage and plan → Slack mention → SMS escalation → reply written in chat → human OK → threaded email back to the customer.
What we learned
Let code do the deterministic work, such as MIME parsing, threading headers, validation and the send path. Let the AI do only the judgment calls: what this mail is, who should get it, and how hard to push. That split made the system both smarter and safer.
What's next for Tsukinowa
- More spokes on both sides: Microsoft Teams, Chatwork and LINE WORKS for chat, and WhatsApp (already in the code) once a business account is approved.
- Hub-to-hub federation: when two companies both run Tsukinowa, their hubs can talk directly.
- AI-drafted replies: "tell them we'll fix it by Friday" becomes a full email that the owner approves.
Built With
- amazon-api-gateway
- amazon-bedrock
- amazon-cloudfront
- amazon-dynamodb
- amazon-eventbridge
- amazon-route-53
- amazon-ses
- amazon-sqs
- amazon-web-services
- aws-cdk
- aws-end-user-messaging
- aws-lambda
- aws-secrets-manager
- claude
- node.js
- slack
- typescript
Log in or sign up for Devpost to join the conversation.