Use QuotaGuard Static with the SendGrid HTTPS API

Configure a dedicated SendGrid HTTP client to use QuotaGuard Static, verify its outbound address, and validate a Mail Send request before sending an approved live message. The result is an explicit application egress path that you can register with SendGrid IP Access Management.

This guide covers the connection from your server-side application to the SendGrid API. It includes a Python HTTPX implementation and a curl-based alternative. Keep using SendGrid for email delivery, sender authentication, and any dedicated sending IPs in your email program.

Requirements

You will need:

  • A QuotaGuard Static account, its complete outbound HTTP proxy URL, and both assigned static IPs.
  • A SendGrid account and an API key with the permissions required by your application. The examples need Mail Send permission.
  • A sender identity configured in SendGrid and a recipient approved for any later live send.
  • Access to the environment that actually sends email requests, including workers or scheduled jobs.
  • Python 3.10 or later. The main example uses HTTPX 0.28.x. The alternative uses Python’s standard library and curl.
  • Permission to change SendGrid access controls and an approved administrative access path.

For the normal global SendGrid endpoint, use https://api.sendgrid.com. An EU regional subuser uses https://api.eu.sendgrid.com. Use the endpoint appropriate to the SendGrid account configuration; a proxy region does not convert a global account into an EU regional subuser. Mail Send endpoint reference

Step 1: identify every client that accesses the SendGrid account

List every caller before enabling or tightening SendGrid IP Access Management:

  • Web processes that call the API directly.
  • Email queue workers and retry workers.
  • Scheduled tasks and one-off administrative scripts.
  • Tools that access templates, contacts, or reporting APIs.
  • SMTP clients using the same account.
  • Administrators who need to access the SendGrid UI.

Assign an owner and an approved network route to each client. Configure the process that opens the SendGrid connection, not only the process that enqueues a job.

IP Access Management applies to the UI, API, and SMTP relay, and becomes active when the first allowed address is added. Include your administrator’s current approved address and plan for a stable future administrator route before saving that change. The application proxy does not automatically route your browser. SendGrid IP Access Management

Step 2 Store the connection values

Set the following values through the runtime’s secrets or protected environment settings. This table describes the values; it is not a request to paste secrets into a support ticket.

Variable Value
QUOTAGUARDSTATIC_URL Complete HTTP proxy URL copied from the QuotaGuard Static dashboard
QG_EXPECTED_IPS Both assigned QuotaGuard IPs, separated by a comma
SENDGRID_API_KEY SendGrid API key with the necessary permissions
SENDGRID_FROM_EMAIL Sender address configured in SendGrid
SENDGRID_TO_EMAIL Approved recipient for a controlled live check
SENDGRID_REGION global, or eu for the appropriate regional subuser

A proxy URL has the form http://USER:PASSWORD@HOST:PORT. Copy the full dashboard value rather than constructing it from an example. Preserve any URL encoding in credentials. The HTTP proxy URL and the HTTPS SendGrid destination have different roles; do not change the proxy scheme to https simply because SendGrid uses HTTPS.

Keep proxy credentials and the API key separate. The proxy authenticates the network connection. The SendGrid key authenticates the API request. Neither replaces the other.

Prefer a mail-scoped key to an account-wide key. Use separate secrets for environments where possible, rotate them through your normal process, and do not put credentials in a client-side application. SendGrid API key scopes

Step 3 Create the dedicated HTTP client

Install the dependency in the application’s Python environment:

python -m pip install 'httpx>=0.28,<0.29'

Save the following as sendgrid_httpx.py. It supports three explicit modes:

  • ip checks the proxy’s egress address without a SendGrid key.
  • sandbox validates a Mail Send request without delivery.
  • live sends one message and requires a separate confirmation environment value.

The client does not follow redirects or retry failed requests. It uses an explicit proxy, keeps TLS verification enabled, and ignores ambient proxy settings. The live recipient is always supplied through the trusted runtime environment rather than request parameters from an unauthenticated caller.

#!/usr/bin/env python3
"""Check QuotaGuard egress, validate SendGrid mail, or explicitly send one message."""
import argparse
import ipaddress
import os
import sys
from urllib.parse import urlsplit

import httpx

API_ORIGINS = {
    "global": "https://api.sendgrid.com",
    "eu": "https://api.eu.sendgrid.com",
}


def required(name):
    value = os.environ.get(name, "")
    if not value or any(ord(c) < 32 or ord(c) == 127 for c in value):
        raise ValueError(f"Set a valid {name} in the trusted runtime environment")
    return value


def proxy_url():
    value = required("QUOTAGUARDSTATIC_URL")
    parsed = urlsplit(value)
    if not (parsed.scheme == "http" and parsed.hostname and parsed.port
            and parsed.username and parsed.password
            and parsed.path in ("", "/") and not parsed.query and not parsed.fragment):
        raise ValueError("Use the complete HTTP proxy URL from the QuotaGuard Static dashboard")
    return value


def expected_ips():
    values = [v.strip() for v in required("QG_EXPECTED_IPS").split(",")]
    addresses = {str(ipaddress.ip_address(v)) for v in values}
    if len(addresses) != 2:
        raise ValueError("QG_EXPECTED_IPS must contain the two assigned addresses, comma-separated")
    return addresses


def mail_payload(live=False):
    return {
        "personalizations": [{"to": [{"email": required("SENDGRID_TO_EMAIL")}]}],
        "from": {"email": required("SENDGRID_FROM_EMAIL")},
        "subject": "QuotaGuard SendGrid connection check",
        "content": [{"type": "text/plain", "value": "This is an authorized connection check."}],
        "mail_settings": {"sandbox_mode": {"enable": not live}},
    }


def run(mode):
    # This client is private to the SendGrid workflow. Do not reuse it for unrelated APIs.
    proxy = proxy_url()
    if mode == "ip":
        allowed = expected_ips()
        url = "https://ip.quotaguard.com"
        payload = None
        headers = {}  # Never send the SendGrid key to the public IP checker.
    else:
        region = os.environ.get("SENDGRID_REGION", "global")
        if region not in API_ORIGINS:
            raise ValueError("SENDGRID_REGION must be global or eu")
        if mode == "live" and os.environ.get("SENDGRID_CONFIRM_LIVE_SEND") != "YES":
            raise ValueError("Live mode needs SENDGRID_CONFIRM_LIVE_SEND=YES after recipient approval")
        url = API_ORIGINS[region] + "/v3/mail/send"
        payload = mail_payload(live=mode == "live")
        headers = {"Authorization": "Bearer " + required("SENDGRID_API_KEY")}

    # trust_env=False also ignores ambient CA settings. Supply an approved SSLContext
    # explicitly if your organization requires a custom CA bundle. Never disable verification.
    with httpx.Client(proxy=proxy, trust_env=False, follow_redirects=False,
                      verify=True, timeout=httpx.Timeout(30.0, connect=10.0)) as client:
        if mode == "ip":
            response = client.get(url)
        else:
            response = client.post(url, headers=headers, json=payload)

    if mode == "ip":
        if response.status_code != 200:
            print(f"IP check returned HTTP {response.status_code}; inspect routing before proceeding")
            return 1
        address = str(ipaddress.ip_address(response.json()["ip"]))
        if address not in allowed:
            print("IP check did not match either assigned address; stop before changing the allowlist")
            return 1
        print(f"Verified assigned QuotaGuard egress IP: {address}")
        return 0

    expected = 202 if mode == "live" else 200
    if response.status_code != expected:
        # Response bodies can contain message data. Keep them out of shared logs.
        print(f"SendGrid returned HTTP {response.status_code}; see the troubleshooting guide")
        return 1
    print("SendGrid accepted the message; delivery is a separate outcome" if mode == "live"
          else "SendGrid accepted the sandbox request; no email was delivered")
    return 0


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("mode", choices=["ip", "sandbox", "live"])
    args = parser.parse_args()
    try:
        return run(args.mode)
    except (ValueError, KeyError, TypeError):
        print("Configuration or response validation failed; check required values without logging secrets", file=sys.stderr)
        return 2
    except (httpx.RequestError, httpx.InvalidURL):
        print("Network or proxy request failed; no automatic retry was made", file=sys.stderr)
        return 3


if __name__ == "__main__":
    sys.exit(main())

HTTPX uses the proxy argument for an explicit proxy connection. Setting trust_env=False also ignores ambient CA variables such as SSL_CERT_FILE. If your deployment needs an approved custom trust store, pass a suitable SSL context explicitly. Do not solve certificate errors by setting verify=False. HTTPX proxies and environment behavior

The configured timeouts govern connection and network operations, not a guaranteed total wall-clock execution limit. Align your job timeout with the application’s operational requirements. HTTPX timeouts

Step 4 Verify egress from the sending runtime

After configuring QUOTAGUARDSTATIC_URL and QG_EXPECTED_IPS, run:

python sendgrid_httpx.py ip

No SendGrid API key is required for this mode. It sends one request to the QuotaGuard IP checker and verifies that the returned address matches either of the two addresses configured in QG_EXPECTED_IPS.

For example, successful output has this form:

Verified assigned QuotaGuard egress IP: 192.0.2.10

The address above is a documentation example, not a QuotaGuard address to use in your account. Your output must match your own dashboard.

Run the check in each sending runtime, such as a web service and a separate worker, if both call SendGrid. A successful laptop check does not prove that a deployed worker has the correct secret or client configuration.

Record both dashboard IPs even if repeated checks show only one. Keep both addresses available in the destination allowlist. QuotaGuard egress verification

Step 5 Configure SendGrid IP Access Management

With the approved administrator route available:

  1. Open the SendGrid console and go to Settings, then IP Access Management.
  2. Review the existing list and the caller inventory from Step 1.
  3. Add both assigned QuotaGuard static IPs.
  4. Include the required administrator and other legitimate client addresses.
  5. Review the complete set before saving.
  6. Confirm that the administrator can still access the account through the approved route.

Do not use a broad cloud-provider range merely to avoid configuring the actual caller. Do not remove existing legitimate entries as part of this setup unless their owners have approved the migration and verified their new routes.

For a first-time restriction, coordinate the change so legitimate clients are covered when the list becomes active. For an existing restriction, add the new path before switching the application. Keep the old approved path until the migration check is complete.

SendGrid’s Recent Access Attempts view can help correlate an attempt with a source IP, time, and access method. Verify the origin of an unfamiliar address before allowing it. SendGrid IP Access Management

Step 6 Validate the API request without delivery

Supply the SendGrid environment values, then run:

python sendgrid_httpx.py sandbox

This posts a complete Mail Send payload with mail_settings.sandbox_mode.enable set to the JSON boolean true. A successful response is HTTP 200. The script prints a concise confirmation without printing the API key, payload, or recipient address.

Sandbox mode does not deliver email, consume email credits, or generate Email Activity or Event Webhook events. It checks the request’s structure and parameters. It does not validate the contents of a referenced template. SendGrid sandbox mode

If the request fails, keep the restriction in place while checking the layer that failed. A proxy authentication problem, a missing API permission, and an invalid Mail Send payload need different fixes. Use the troubleshooting guide rather than automatically broadening the allowlist.

Step 7 Perform one approved live check

Only after the recipient and message are approved, set SENDGRID_CONFIRM_LIVE_SEND to YES in the trusted runtime and run:

python sendgrid_httpx.py live

This mode disables sandbox validation and requests actual delivery. Remove the confirmation value after the check so a later accidental command does not reuse it.

A normal HTTP 202 response means SendGrid accepted the request. Inspect SendGrid’s email activity or your normal event-processing path to establish what happened afterward. Do not report 202 as proof that the recipient received the message. Mail Send API reference

Do not enable automatic retries for live checks. A timeout after submission can leave the acceptance outcome uncertain. Review that outcome before sending another copy.

Use curl for an independent client check

The companion sendgrid_curl.py file runs curl with the proxy configuration and authorization header supplied through standard input. It avoids putting the secret-bearing proxy URL or API key in curl’s argument list.

Use it when you want a curl-based check without changing the application’s HTTPX configuration:

python sendgrid_curl.py ip
python sendgrid_curl.py sandbox

The helper accepts no live mode. It uses the same environment variables, disables ambient proxy bypasses, does not follow redirects, and does not retry. It does not print raw curl errors or response bodies because those can contain sensitive details.

Keep the helper beside the application example or include it in your operational toolkit. The public IP check requires only Python and curl; it needs no SendGrid key. curl configuration reference

Save this helper as sendgrid_curl.py:

#!/usr/bin/env python3
"""Use curl for a QuotaGuard IP check or a non-delivering SendGrid sandbox request."""
import argparse
import ipaddress
import json
import os
import subprocess
import sys
from urllib.parse import urlsplit


def required(name):
    value = os.environ.get(name, "")
    if not value or any(ord(c) < 32 or ord(c) == 127 for c in value):
        raise ValueError("Missing or invalid configuration")
    return value


def quote(value):
    if any(ord(c) < 32 or ord(c) == 127 for c in value):
        raise ValueError("Control characters are not allowed in curl configuration")
    return '"' + value.replace("\\", "\\\\").replace('"', '\\"') + '"'


def configuration(mode):
    proxy = required("QUOTAGUARDSTATIC_URL")
    parsed = urlsplit(proxy)
    if not (parsed.scheme == "http" and parsed.hostname and parsed.port
            and parsed.username and parsed.password and parsed.path in ("", "/")
            and not parsed.query and not parsed.fragment):
        raise ValueError("Use the dashboard HTTP proxy URL")
    lines = ["silent", "show-error", "connect-timeout = 10", "max-time = 30",
             'proto = "=https"', 'noproxy = ""', 'retry = 0',
             "proxy = " + quote(proxy), 'write-out = "\\n%{http_code}"']
    if mode == "ip":
        lines.append('url = "https://ip.quotaguard.com"')
    else:
        origins = {"global": "https://api.sendgrid.com", "eu": "https://api.eu.sendgrid.com"}
        region = os.environ.get("SENDGRID_REGION", "global")
        if region not in origins:
            raise ValueError("Invalid SendGrid region")
        payload = {
            "personalizations": [{"to": [{"email": required("SENDGRID_TO_EMAIL")}]}],
            "from": {"email": required("SENDGRID_FROM_EMAIL")},
            "subject": "QuotaGuard SendGrid sandbox check",
            "content": [{"type": "text/plain", "value": "Sandbox validation only."}],
            "mail_settings": {"sandbox_mode": {"enable": True}},
        }
        lines += ["url = " + quote(origins[region] + "/v3/mail/send"),
                  'request = "POST"', 'header = "Content-Type: application/json"',
                  "header = " + quote("Authorization: Bearer " + required("SENDGRID_API_KEY")),
                  "data-raw = " + quote(json.dumps(payload))]
    return "\n".join(lines) + "\n"


def run(mode):
    allowed = None
    if mode == "ip":
        allowed = {str(ipaddress.ip_address(v.strip()))
                   for v in required("QG_EXPECTED_IPS").split(",")}
        if len(allowed) != 2:
            raise ValueError("Supply both assigned addresses")
    config = configuration(mode)
    # Secrets enter curl over stdin, not argv. -q must be the first curl option.
    # Remove ambient proxy settings; noproxy="" also prevents an ambient bypass.
    env = {k: v for k, v in os.environ.items()
           if k.lower() not in {"http_proxy", "https_proxy", "all_proxy", "no_proxy"}
           and not k.startswith(("SENDGRID_", "QUOTAGUARD", "QG_"))}
    result = subprocess.run(["curl", "-q", "--config", "-"], input=config,
                            capture_output=True, text=True, env=env, timeout=35,
                            check=False)
    if result.returncode:
        print(f"curl failed with exit code {result.returncode}; details withheld to protect secrets")
        return 3
    body, separator, status = result.stdout.rpartition("\n")
    if not separator or status != "200":
        print("Request did not return expected HTTP 200; inspect routing or SendGrid configuration")
        return 1
    if mode == "ip":
        address = str(ipaddress.ip_address(json.loads(body)["ip"]))
        if address not in allowed:
            print("IP check did not match an assigned QuotaGuard address")
            return 1
        print(f"Verified assigned QuotaGuard egress IP: {address}")
    else:
        print("SendGrid accepted the sandbox request; no email was delivered")
    return 0


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("mode", choices=["ip", "sandbox"])
    args = parser.parse_args()
    try:
        return run(args.mode)
    except (ValueError, KeyError, TypeError, OSError, subprocess.TimeoutExpired):
        print("Configuration, response, or process check failed; no automatic retry was made", file=sys.stderr)
        return 2


if __name__ == "__main__":
    sys.exit(main())

Adapt the pattern to your application

Background workers

Initialize or reuse a dedicated SendGrid client within the worker process according to the worker framework’s lifecycle. Do not share a live connection pool across process forks without following the client’s documented lifecycle guidance. Set the proxy secret on all workers that may process the queue.

Use a minimal controlled job to verify the route after deployment. Check the worker that sends the message, not merely the web endpoint that returns “queued.”

Other SendGrid API operations

The same routing pattern can be applied to HTTPS operations your application is authorized to perform, such as reading templates. Use the correct endpoint and permission scope. Do not expand a mail-only API key to full account access for convenience.

Keep fixed, approved destination hosts in application configuration. Do not turn a SendGrid-specific helper into a general URL-fetching endpoint that can send credentials to arbitrary destinations.

SDKs

If you use an official SendGrid SDK, inspect its supported HTTP transport options for the exact version installed. Configure the component that opens the socket. Run an egress check and an actual SendGrid sandbox call through that same transport.

A successful curl check establishes the curl route. It does not prove an SDK uses the same route. A global proxy variable can also affect unrelated libraries, so prefer a scoped client when practical.

SMTP and incoming webhooks

This guide’s code calls the HTTPS API. It does not configure SMTP, and an HTTP proxy argument should not be copied into an SMTP client’s configuration without reviewing the required protocol support.

SendGrid Event Webhooks travel from SendGrid toward your application. They are a separate inbound path. Keep your existing webhook authentication and verification controls; changing the application’s outbound route does not authenticate incoming events. SendGrid Event Webhook security

Operate and roll back deliberately

Record the expected IP pair, account owner, runtime, and change date in your internal runbook. Keep secrets in the secret manager rather than the runbook.

Monitor status codes, latency, queue age, and retry volume. Track an internal job identifier without logging full message bodies or recipient lists. Do not silently switch to direct egress if the proxy fails.

If a deployment fails, pause affected send workers when needed to avoid duplicate attempts. Restore the last known working client configuration or approved network path. Check whether requests may already have been accepted before replaying jobs.

Do not disable SendGrid restrictions or remove the administrator’s allowed route as a routine rollback. If a security policy change is necessary, obtain approval from the account owner and document the temporary state.

Completion checklist

  • Both assigned QuotaGuard IPs are recorded and allowed.
  • The administrator’s approved browser path still works.
  • Every API-sending runtime uses the configured client.
  • A proxy IP check returns an assigned address.
  • A complete sandbox request returns HTTP 200.
  • An approved live check returns HTTP 202 and its downstream outcome is inspected.
  • No secret-bearing URL, authorization header, or full email payload appears in shared logs.
  • Retry, timeout, and rollback behavior match the application’s email requirements.

See QuotaGuard plans for shared and dedicated IP options, and SendGrid’s access management documentation for the account-side controls.

Download the reference examples · Integration overview


Ready to Get Started?

Get in touch or create a free trial account

Back to top ↑

Copyright © 2009 - 2026 QuotaGuard. All rights reserved.