Email

Send test messages through local SMTP, SendGrid, Amazon SES, or Azure Communication Services and inspect the captured mail.

What Sqrzl does

Sqrzl accepts outbound mail requests and stores each message locally for inspection. It does not relay mail or deliver it to real inboxes. Start the emulator with the quickstart before connecting an SDK.

Choose an endpoint

TransportLocal endpointUse it for
SMTPlocalhost:2525Applications that send mail through an SMTP client.
SendGrid Mail Send v3http://localhost:9000SendGrid SDKs and requests to /v3/mail/send.
Amazon SES v2http://localhost:9000SES v2 SendEmail with simple content.
Azure Communication Services Emailhttp://localhost:9000ACS Email send and operation polling.

The API providers share the HTTP listener with the storage APIs. Set the SDK endpoint or base URL to http://localhost:9000.

Connect an SDK

SMTP with Python

import smtplib
from email.message import EmailMessage

message = EmailMessage()
message["From"] = "sender@example.com"
message["To"] = "recipient@example.com"
message["Subject"] = "Local Sqrzl test"
message.set_content("This message is captured locally.")

with smtplib.SMTP("127.0.0.1", 2525, timeout=5) as client:
    client.send_message(message)

SMTP is plaintext and accepts strict angle-bracket envelope paths. Use a client that does not require SMTP AUTH or STARTTLS.

SendGrid

import sendgrid
from sendgrid.helpers.mail import Mail

client = sendgrid.SendGridAPIClient(api_key="local-key")
client.client.host = "http://localhost:9000"
message = Mail(
    from_email="sender@example.com",
    to_emails="recipient@example.com",
    subject="Local Sqrzl test",
    plain_text_content="This message is captured locally.",
)
response = client.send(message)

If SQRZL_SENDGRID_API_KEY is set on Sqrzl, pass the same key to the SDK. If it is unset, the adapter does not require a SendGrid bearer token.

Amazon SES v2

import boto3
from botocore.config import Config

client = boto3.client(
    "sesv2",
    endpoint_url="http://localhost:9000",
    aws_access_key_id="admin",
    aws_secret_access_key="sqrzl-secret",
    region_name="us-east-1",
    config=Config(signature_version="s3v4"),
)
client.send_email(
    FromEmailAddress="sender@example.com",
    Destination={"ToAddresses": ["recipient@example.com"]},
    Content={"Simple": {
        "Subject": {"Data": "Local Sqrzl test"},
        "Body": {"Text": {"Data": "This message is captured locally."}},
    }},
)

The credentials above match the checked-in Compose defaults. Use the values configured for your local instance if you changed them.

Azure Communication Services Email

import base64
from azure.communication.email import EmailClient
from azure.core.pipeline.transport import RequestsTransport

class LocalHttpTransport(RequestsTransport):
    def send(self, request, **kwargs):
        # The SDK signs its HTTPS URL; Sqrzl serves local HTTP.
        if request.url.startswith("https://"):
            request.url = "http://" + request.url.removeprefix("https://")
        return super().send(request, **kwargs)

access_key = base64.b64encode(b"shared-secret").decode("ascii")
connection = (
    "endpoint=http://localhost:9000;accesskey=" + access_key
)
client = EmailClient.from_connection_string(
    connection,
    transport=LocalHttpTransport(),
)
poller = client.begin_send({
    "senderAddress": "sender@example.com",
    "recipients": {"to": [{"address": "recipient@example.com"}]},
    "content": {
        "subject": "Local Sqrzl test",
        "plainText": "This message is captured locally.",
    },
})
poller.result(timeout=10)

Configure SQRZL_ACS_CONNECTION_STRING on Sqrzl with the same base64 access key. This transport adapts the official SDK to the emulator's local HTTP endpoint after the SDK signs the request.

Configure authentication

SettingEffect
SQRZL_ACCESS_KEY_ID and SQRZL_SECRET_ACCESS_KEYSet both to enable shared provider and admin authentication. The default Compose values are admin and sqrzl-secret.
SQRZL_SENDGRID_API_KEYRequire this bearer token on the SendGrid adapter when set.
SQRZL_ACS_CONNECTION_STRINGSet endpoint=<url>;accesskey=<base64> to enable ACS HMAC authentication for Email and SMS.

SES requests use SigV4. SMTP has no authentication. See configuration for the complete environment settings.

Inspect captured mail

Open http://localhost:9001, sign in if admin authentication is enabled, and choose Mailboxes. Sqrzl creates a mailbox for each recipient and shows the captured message content, headers, and attachments where supported.

All adapters capture locally. A successful provider-shaped response means the request was accepted by the emulator; it does not mean a remote mail server sent or delivered the message.

Supported behavior

  • SMTP accepts a plaintext subset of EHLO/HELO, MAIL FROM, RCPT TO, DATA, RSET, NOOP, and QUIT. Recipient fan-out is validated and captured atomically.
  • SendGrid supports selected personalizations, to/cc/bcc recipients, plain and HTML content, headers, reply-to fields, and base64 attachments. Its accepted request size is under 30 MB.
  • SES v2 supports Content.Simple, destinations, reply-to addresses, subject and body charset metadata, and selected custom headers. It does not support attachments.
  • ACS Email supports API versions 2023-03-01, 2023-03-31, and 2025-09-01, up to 50 combined to/cc/bcc recipients, plain or HTML content, and base64 regular or inline attachments up to 10 MB. Operation IDs and five-minute repeatability are supported.

SMTP AUTH, STARTTLS, relay and delivery, SES raw and template content, SendGrid templates and scheduling, and ACS delivery events are outside the supported scope. The provider-specific limits and rejected features are listed in the compatibility matrix.

Request and capture limits

SQRZL_MAX_REQUEST_BYTES defaults to 128 MiB per HTTP request body or SMTP DATA payload. Separately, each capture transaction has a conservative 64 MiB projected aggregate materialization limit. It accounts for fan-out copies, raw MIME, attachments, and stored records.

Oversized HTTP requests receive a provider-shaped 413; SMTP returns 552. Rejected requests do not leave partial mailbox captures. See configuration for the request-body limit.