# How the Email Suppression Handler Works and Manages Gmail Attachments in Macro

> Discover how the email suppression handler manages Gmail attachments. This AWS Lambda function processes bounce notifications and fetches attachments on-demand directly from the Gmail API.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: deep-dive
- Published: 2026-08-18

---

**The email suppression handler is an AWS Lambda function that processes Amazon SES bounce and complaint notifications via SNS to maintain a real-time blocked-email list, while forwarded Gmail attachments are fetched on-demand directly from the Gmail API rather than stored in Macro's own infrastructure.**

The `macro-inc/macro` repository implements a robust email deliverability system that safeguards outgoing message reputation by automatically suppressing problematic addresses. Understanding how this handler processes SES notifications and manages external attachment sources reveals the architecture behind reliable email delivery in the Macro platform.

## How the Email Suppression Handler Processes SES Events

The suppression pipeline begins at the AWS Lambda entry point in [`services/email_suppression_handler/src/handler.rs`](https://github.com/macro-inc/macro/blob/main/services/email_suppression_handler/src/handler.rs). The `handler` function receives an `SnsEvent` containing one or more SES notification messages, dispatching each record to `handle_email_notification` for processing.

### Entry Point and Deserialization

Inside `handle_email_notification`, the raw JSON payload from SNS is deserialized into the `EmailNotification` enum defined in [`services/email_suppression_handler/src/model.rs`](https://github.com/macro-inc/macro/blob/main/services/email_suppression_handler/src/model.rs). This enum distinguishes between `NotificationType::Bounce` and `NotificationType::Complaint` events using `serde_json`, routing each type to its specific handler function.

```rust
// Simplified example of processing a bounce notification
let payload = r#"
{
  "notificationType": "Bounce",
  "bounce": {
    "bounceType": "Permanent",
    "bounced_recipients": [{"emailAddress": "bad@example.com"}]
  }
}
"#;

// Inside the Lambda handler
handle_email_notification(&db_pool, payload).await?;

```

### Handling Permanent Bounces

The `handle_bounce_notification` function inspects the `bounce_type` field to determine suppression behavior. When the type is **Permanent**, indicating a hard bounce, every address in the `bounced_recipients` array is collected and passed to the database layer. **Transient** bounces (such as `MailboxFull` or `AttachmentRejected`) are logged but do not trigger blocks, and **Undetermined** bounces receive similar treatment to avoid suppressing valid addresses prematurely.

### Processing Complaint Notifications

For complaints handled by `handle_complaint_notification`, the logic checks for a `complaint_sub_type` first. If present (for example, `OnAccountSuppressionList`), the system treats this as a hard suppression immediately. When no sub-type exists, the handler inspects `complaint_feedback_type`:

- **Triggers block**: `Abuse`, `AuthFailure`, `Fraud`, `Virus`, `Other`
- **Ignored**: `NotSpam`

This granular filtering ensures only legitimate abuse signals affect deliverability while preserving send capability for false-positive reports.

## Database Operations for Blocked Emails

Once the handler identifies addresses requiring suppression, it calls `macro_db_client::blocked_email::bulk_upsert_block_email` from [`crates/macro_db_client/src/blocked_email.rs`](https://github.com/macro-inc/macro/blob/main/crates/macro_db_client/src/blocked_email.rs). This function performs idempotent upserts into the `blocked_email` table, ensuring that duplicate notifications do not create multiple records while enabling fast lookups by the email-sending services before dispatching future messages.

## How Gmail Attachments Are Managed in Macro

Unlike standard attachments that might reside in S3, forwarded Gmail attachments follow a zero-storage-retrieval pattern. The system fetches raw attachment bytes directly from Google's servers at send-time, eliminating storage duplication and ensuring recipients receive the latest version of the file.

### On-Demand Fetching from Gmail API

The `GmailApi` client defined in [`services/email_service/src/util/gmail/send.rs`](https://github.com/macro-inc/macro/blob/main/services/email_service/src/util/gmail/send.rs) and [`services/email_service/src/util/upload_attachment.rs`](https://github.com/macro-inc/macro/blob/main/services/email_service/src/util/upload_attachment.rs) handles authentication with Google before calling `users.messages.attachments.get`. The `fetch_attachment` method constructs requests using the Gmail message ID and attachment ID, respects Gmail-specific rate-limit headers, and converts the Base-64 encoded response into raw bytes for inclusion in the outgoing email.

```rust
let gmail_api = GmailApi::for_account(&account).await?;
let attachment_data = gmail_api
    .fetch_attachment(message_id, attachment_id)
    .await
    .context("Failed to fetch attachment from Gmail")?;
email.send_with_attachment(attachment_data).await?;

```

### Error Handling for Attachment Retrieval

If `fetch_attachment` encounters network failures, permission errors, or missing resources, it returns a `GmailFetchFailed` error that bubbles up to the caller. This fail-fast approach prevents partial message delivery, ensuring that emails missing critical attachments are never sent to recipients.

## Summary

- **The email suppression handler** is an AWS Lambda function in [`services/email_suppression_handler/src/handler.rs`](https://github.com/macro-inc/macro/blob/main/services/email_suppression_handler/src/handler.rs) that processes Amazon SES notifications via SNS to maintain deliverability.
- **Permanent bounces** and specific complaint types trigger immediate insertion into the `blocked_email` table via `bulk_upsert_block_email`, while transient bounces are logged only.
- **Gmail attachments** are retrieved on-demand from the Gmail API at send-time rather than stored in Macro's S3 buckets, utilizing the `GmailApi` client in [`services/email_service/src/util/gmail/send.rs`](https://github.com/macro-inc/macro/blob/main/services/email_service/src/util/gmail/send.rs).
- **Error handling** for attachment fetching is strict, with `GmailFetchFailed` errors preventing message delivery if attachments cannot be retrieved.

## Frequently Asked Questions

### What triggers an email address to be added to the blocked list?

According to the source code in [`services/email_suppression_handler/src/handler.rs`](https://github.com/macro-inc/macro/blob/main/services/email_suppression_handler/src/handler.rs), addresses are blocked when SES reports a **Permanent** bounce type or when complaint notifications contain specific sub-types like `OnAccountSuppressionList` or feedback types including `Abuse`, `Fraud`, or `Virus`. Transient bounces and `NotSpam` complaints do not trigger blocks.

### Why doesn't Macro store Gmail attachments in S3?

The architecture avoids storing Gmail attachments in Macro's infrastructure to prevent data duplication and ensure recipients always receive the most current version of the file. By fetching directly from the Gmail API at send-time using the `fetch_attachment` method in [`services/email_service/src/util/gmail/send.rs`](https://github.com/macro-inc/macro/blob/main/services/email_service/src/util/gmail/send.rs), the system maintains lightweight storage while guaranteeing attachment freshness.

### What happens when the Gmail API fails to return an attachment?

If the `GmailApi` client encounters errors during `fetch_attachment`, it returns a `GmailFetchFailed` error that propagates up the call stack. This causes the entire send operation to fail immediately, ensuring that Macro never delivers emails advertising attachments that weren't successfully retrieved from Gmail's servers.

### How does the handler distinguish between bounce severity levels?

The handler examines the `bounceType` field within SES notifications. **Permanent** bounces indicate non-existent domains or addresses and trigger immediate blocking. **Transient** bounces (like full mailboxes) and **Undetermined** bounces are logged for observability but do not result in address suppression, preserving the ability to retry delivery to those recipients later.