Halit Software← Back
2025-08-227 min

How to Process Inbound Emails with AWS SES and Lambda

Background: Why Reply-by-Email Matters

Most web applications send notification emails when new comments are posted. But for users, replying often requires clicking a link, logging in, and navigating back to the portal. That friction means fewer replies and lower engagement.

The natural user behavior is to simply hit "Reply" in their email client. If we can safely capture that reply, parse it, and link it back to the correct thread, we create a seamless experience:

  • Users engage more, because they don't have to leave their inbox.
  • Replies remain structured in the portal, tied to the right resource and conversation.
  • The system stays secure — no risk of spoofed emails or misplaced replies.

This was the problem we set out to solve for one of our client portals.

Use Case

The portal already had a comment feature where authors get email notifications when someone comments on their resource. Our goal was to let those authors reply directly by email, and have that response show up as a new comment inside the portal.

Constraints we had to address:

  • Keep user workflow unchanged (just hit reply).
  • Prevent abuse or spoofing.
  • Map each reply securely to the correct resource, comment, and user.
  • Keep the system lightweight and low-maintenance.

High-Level AWS Architecture

We built the feature using AWS SES inbound email processing, S3, and a Lambda parser written in TypeScript.

Flow

  1. A user posts a new comment for a resource in the portal.
  2. Notification email includes a reply-to address on a dedicated subdomain (e.g., @reply.example.com).
  3. User replies from their mail client.
  4. AWS SES (inbound) accepts the email and:
    • Stores the raw EML in S3.
    • Invokes a Lambda function with the S3 object reference.
  5. Lambda:
    • Fetches the raw EML.
    • Parses it (strips quoted text, handles MIME).
    • Decrypts the reply-to token from the email address.
  6. Lambda calls the Portal API to create the comment.
  7. The Portal shows the new comment in the correct thread.

Designing the Reply-To Token

A critical piece is the reply-to token, embedded in the reply address itself.

What it contains:

  • version, stage, type (for future-proofing).
  • Identifiers: resource ID, comment ID, user ID.

How it's protected:

  • Encrypted with AES-GCM using Node.js crypto.
  • Provides authenticity (tamper detection) and confidentiality.

Trade-off:

Because email addresses have a ~64-character local-part limit, we had to keep the token short. This led us to omit an explicit IV in the token structure. While this breaks the usual AES-GCM best practice, the risk was acceptable in our closed, controlled use case. (We'll explore safer compact alternatives later.)

Email Parsing Challenges

Email replies are messy in practice:

  • Clients include quoted history (On … wrote:).
  • MIME can include plain text, HTML, and inline attachments.
  • Mobile clients behave differently from desktop ones.

We used the mailparser Node.js library in Lambda to extract just the latest reply, tested across Gmail, Outlook, and iOS clients.

Why AWS SES + Lambda?

We compared alternatives, but this stack fit best:

  • SES Inbound Email: Scalable, pay-as-you-go SMTP edge with domain auth (SPF, DKIM, DMARC).
  • S3 Storage: Durable raw message archive for debugging or reprocessing.
  • Lambda: Serverless execution — no infrastructure to manage, auto-scales, tight integration with SES/S3.
  • Portal API Integration: Simple hand-off once reply is validated.

The result: a serverless, low-maintenance pipeline.

Lessons and Trade-offs

  • Token length vs. security: 64-character email constraint forced trade-offs.
  • Direct SES → Lambda flow: Simpler now, but SNS fan-out could be added later.
  • Observability: Started with CloudWatch logs; will expand to metrics/alarms and DLQs in future.
  • Parsing reality: Must test with multiple clients to avoid broken experiences.