Automating PDF Badge Delivery via AWS SES: Resolving MIME Boundary Corruption and Dynamic Field Mapping Drift

Symptom Identification Link to this section

Event operations teams and registration managers observe silent delivery degradation during high-throughput badge distribution. Primary failure modes include:

  • Truncated or Corrupted Attachments: Attendees receive PDFs that terminate mid-stream or render as garbled binary. AWS SES SendRawEmail occasionally returns InvalidParameterValue, or succeeds while delivering unreadable payloads.
  • Dynamic Field Mapping Drift: Variable-length metadata (sponsor tiers, VIP designations, dietary tags) shifts layout coordinates, causing QR codes and barcodes to misalign or exceed printable margins.
  • Hardware Rejection at Print Stations: Zebra/Dymo on-site printers reject SES-delivered files due to missing DPI metadata, raster threshold violations, or layout overflow.
  • Pipeline Stalls: Corrupted payloads propagate downstream, halting PDF Routing Workflows and forcing manual check-in interventions.

Root Cause & Explicit Failure Boundaries Link to this section

Failures originate at the intersection of Python MIME construction, AWS SES transport limits, and unguarded template synchronization. Three explicit boundaries must be enforced:

  1. MIME Boundary Corruption: Streaming raw PDF bytes into email.mime objects without explicit Content-Transfer-Encoding: base64 and strict \r\n boundary isolation causes SES to misinterpret unescaped newlines as multipart terminators. SES silently drops trailing bytes.
  2. Template Drift & Layout Overflow: Unpinned template versions allow coordinate shifts when dynamic fields exceed predefined bounding boxes. This directly impacts QR Code Generation and Barcode Threshold Tuning, pushing scannable elements into non-printable margins.
  3. Label Printer Threshold Violations: SES payloads lacking embedded DPI metadata or vectorized raster thresholds trigger hardware-level rejection on 300 DPI thermal printers expecting exact margin padding and strict grayscale thresholds.

Without deterministic pre-flight validation and RFC 2046-compliant encapsulation, routing pipelines propagate corrupted assets.

Step-by-Step Resolution: MIME-Safe SES Routing with Threshold Validation Link to this section

1. Pre-Flight Dimensional & DPI Validation Link to this section

Validate PDF assets before MIME wrapping. Reject files that violate printer thresholds or exceed layout constraints.

PYTHON
import io
import logging
from pypdf import PdfReader
from reportlab.pdfgen import canvas

logger = logging.getLogger(__name__)

REQUIRED_DPI = 300
MAX_PDF_SIZE_BYTES = 8_000_000  # SES 10MB limit, reserve 2MB for headers/overhead
MAX_FIELD_LENGTH = 120  # Prevents layout overflow

def validate_badge_pdf(pdf_bytes: bytes, attendee_name: str) -> bool:
    """Pre-flight validation for DPI, dimensions, and payload size."""
    if len(pdf_bytes) > MAX_PDF_SIZE_BYTES:
        raise ValueError(f"PDF exceeds {MAX_PDF_SIZE_BYTES} bytes. SES will truncate.")
    
    reader = PdfReader(io.BytesIO(pdf_bytes))
    if len(reader.pages) != 1:
        raise ValueError("Badge PDF must contain exactly one page.")
    
    # Check field length to prevent coordinate drift
    if len(attendee_name) > MAX_FIELD_LENGTH:
        logger.warning(f"Attendee name truncated to {MAX_FIELD_LENGTH} chars to prevent layout overflow.")
        
    # DPI/Dimension validation (simplified: check media box against 300 DPI standard)
    page = reader.pages[0]
    width_pt = float(page.mediabox.width)
    height_pt = float(page.mediabox.height)
    # Standard 4x6 badge at 300 DPI = 1200x1800 pts
    if not (1150 <= width_pt <= 1250 and 1750 <= height_pt <= 1850):
        raise ValueError("PDF dimensions violate 300 DPI badge template constraints.")
        
    return True

2. RFC 2046-Compliant MIME Construction Link to this section

Python’s email package handles boundary generation, but explicit base64 encoding and CRLF enforcement prevent SES transport corruption.

PYTHON
import base64
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.mime.text import MIMEText
from email.utils import formataddr

def build_mime_badge_email(
    to_email: str,
    from_email: str,
    subject: str,
    pdf_bytes: bytes,
    filename: str
) -> bytes:
    """Construct SES-compliant raw MIME message with explicit base64 encoding."""
    msg = MIMEMultipart("mixed")
    msg["From"] = formataddr(("Event Ops", from_email))
    msg["To"] = to_email
    msg["Subject"] = subject
    # Enforce RFC 2046 boundary generation
    msg.preamble = "This is a multi-part message in MIME format."

    # Text body
    text_part = MIMEText("Please find your event badge attached.", "plain", "utf-8")
    msg.attach(text_part)

    # PDF attachment with explicit base64 encoding
    pdf_part = MIMEApplication(pdf_bytes, Name=filename)
    pdf_part["Content-Disposition"] = f'attachment; filename="{filename}"'
    pdf_part["Content-Type"] = "application/pdf"
    # Explicitly set transfer encoding to prevent newline misinterpretation
    pdf_part["Content-Transfer-Encoding"] = "base64"
    
    # Force base64 encoding on the payload
    encoded_payload = base64.b64encode(pdf_bytes).decode("ascii")
    # Chunk into 76-character lines per RFC 2045
    pdf_part.set_payload("\r\n".join(
        [encoded_payload[i:i+76] for i in range(0, len(encoded_payload), 76)]
    ))
    
    msg.attach(pdf_part)
    
    # as_bytes() enforces \r\n line endings and generates a unique boundary
    return msg.as_bytes()

3. SES SendRawEmail Integration & Throughput Handling Link to this section

Use SendRawEmail with explicit error handling and exponential backoff for SES rate limits.

PYTHON
import boto3
from botocore.exceptions import ClientError
import time

ses_client = boto3.client("ses", region_name="us-east-1")

def send_badge_via_ses(raw_mime_bytes: bytes, max_retries: int = 3) -> dict:
    """Dispatch raw MIME payload to SES with retry logic and quota awareness."""
    for attempt in range(max_retries):
        try:
            response = ses_client.send_raw_email(
                RawMessage={"Data": raw_mime_bytes}
            )
            return {"status": "success", "message_id": response["MessageId"]}
        except ClientError as e:
            error_code = e.response["Error"]["Code"]
            if error_code == "Throttling":
                backoff = (2 ** attempt) * 0.5
                time.sleep(backoff)
                continue
            elif error_code == "InvalidParameterValue":
                raise RuntimeError(f"SES rejected payload: {e.response['Error']['Message']}")
            else:
                raise
    raise RuntimeError("SES delivery failed after max retries due to throttling.")

Incident Rollback & Fallback Routing Link to this section

When MIME corruption or template drift is detected in production:

  1. Immediate Circuit Break: Disable automated SendRawEmail dispatch in your routing orchestrator. Switch to synchronous S3 presigned URL generation.
  2. Template Reversion: Pin the last known stable template version. Disable dynamic field injection until bounding box constraints are validated.
  3. Fallback Delivery Pipeline:
PYTHON
  def fallback_delivery(s3_client, bucket: str, key: str, attendee_email: str):
      url = s3_client.generate_presigned_url(
          "get_object", Params={"Bucket": bucket, "Key": key}, ExpiresIn=86400
      )
      # Dispatch plain-text email with link instead of attachment
      # Bypasses MIME boundary risks entirely during incident
  1. Verification: Re-enable SES routing only after validate_badge_pdf() passes 100% of test payloads and SES SendRawEmail returns consistent MessageId without InvalidParameterValue.

Memory & Performance Constraints Link to this section

  • Payload Cap: Strictly enforce 8_000_000 bytes for PDFs. SES enforces a 10 MB total message limit; headers, base64 overhead (~33%), and multipart boundaries consume the remainder.
  • Memory Footprint: Avoid io.BytesIO double-buffering. Generate PDFs directly to bytes, encode to base64 in a single pass, and discard raw bytes before SES dispatch. Peak memory per badge should remain < 15 MB.
  • Throughput Limits: AWS SES default sending quota is 14 messages/second. Implement token-bucket rate limiting or AWS SES configuration sets with event publishing to CloudWatch for real-time bounce/complaint tracking.
  • Garbage Collection: Explicitly del pdf_bytes after MIME construction in high-concurrency workers to prevent heap fragmentation during peak registration windows.