Skip to content

Outbound Email

MailAtlas can compose, render, store, and send outbound email through providers you configure at runtime. Use this workflow when your application needs a local audit record for messages it sends.

Outbound email uses the same local-first design as inbound ingest: MailAtlas writes inspectable artifacts first, then contacts the provider unless the message is a dry run.

MailAtlas is not a hosted deliverability service. It does not manage reputation, suppression lists, campaign analytics, bounce processing, or provider accounts.

Every outbound message gets a local record before provider delivery. That record can include:

  • A rendered raw .eml snapshot.
  • Plain-text body files.
  • HTML body files.
  • Copied attachments.
  • Recipient metadata.
  • BCC metadata in SQLite.
  • Provider name.
  • Status.
  • Error details.
  • Retry metadata.
  • Provider response metadata.

Provider secrets and OAuth tokens are read at runtime. MailAtlas does not write SMTP passwords, Cloudflare API tokens, Gmail access tokens, or Gmail refresh tokens into store.db, raw snapshots, logs, or JSON send results.

Use --dry-run to verify rendering, validation, attachments, and local storage without contacting a provider:

Terminal window
mailatlas send \
--dry-run \
--subject "Build complete" \
--text "The build passed."

The command prints JSON with the outbound record ID and stores rendered files under outbound/ in the workspace root.

Use dry runs when testing message rendering, generated content, attachments, headers, review workflows, or provider setup.

Choose a provider with --provider or MAILATLAS_SEND_PROVIDER.

ProviderUse it whenCredentials
smtpYou already have an SMTP relay or local mail test server.SMTP host, optional username and password.
cloudflareYou use Cloudflare Email Service for API-based sending.Cloudflare account ID and API token.
gmailYou want to send from a personal Gmail address or Gmail send-as alias.Gmail API OAuth token with the gmail.send scope.
Terminal window
export MAILATLAS_SEND_PROVIDER=smtp
export MAILATLAS_SMTP_HOST=smtp.example.com
export MAILATLAS_SMTP_USERNAME=agent@example.com
export MAILATLAS_SMTP_PASSWORD=app-password
mailatlas send \
--subject "SMTP test" \
--text "Sent through SMTP."

Useful SMTP variables:

  • MAILATLAS_SMTP_HOST
  • MAILATLAS_SMTP_PORT
  • MAILATLAS_SMTP_USERNAME
  • MAILATLAS_SMTP_PASSWORD
  • MAILATLAS_SMTP_STARTTLS
  • MAILATLAS_SMTP_SSL

For Gmail addresses, SMTP app passwords are a compatibility path. Prefer the Gmail provider when OAuth is available.

Terminal window
export MAILATLAS_SEND_PROVIDER=cloudflare
export MAILATLAS_CLOUDFLARE_ACCOUNT_ID=your-account-id
export MAILATLAS_CLOUDFLARE_API_TOKEN=your-api-token
mailatlas send \
--subject "Cloudflare test" \
--text "Sent through Cloudflare Email Service."

Useful Cloudflare variables:

  • MAILATLAS_CLOUDFLARE_ACCOUNT_ID
  • MAILATLAS_CLOUDFLARE_API_TOKEN
  • MAILATLAS_CLOUDFLARE_API_BASE

Use the Cloudflare API base override only for tests or provider-compatible gateways.

Use the Gmail provider for personal Gmail addresses and Gmail-configured send-as aliases:

Terminal window
python -m pip install "mailatlas[keychain]"
mailatlas auth gmail \
--client-id "$MAILATLAS_GMAIL_CLIENT_ID" \
--client-secret "$MAILATLAS_GMAIL_CLIENT_SECRET" \
mailatlas send \
--provider gmail \
--subject "Gmail API test" \
--text "Sent with Gmail API OAuth."

MailAtlas requests only the https://www.googleapis.com/auth/gmail.send scope by default.

For backend applications, store Gmail refresh tokens in your own encrypted credential store and pass short-lived access tokens to MailAtlas at send time.

Terminal window
mailatlas send \
--subject "Report" \
--text-file report-summary.txt \
--attach report.pdf \
--header "X-Campaign-ID: weekly"

MailAtlas validates sender and recipient fields, rejects CR/LF header injection, and fails if an attachment path is missing.

BCC recipients are stored in SQLite for audit and are included in provider delivery. They are omitted from local raw MIME snapshots.

Provider behavior:

  • SMTP sends BCC through the SMTP envelope.
  • Cloudflare sends BCC through the provider payload.
  • Gmail API sends use a provider-only transient MIME payload that includes BCC for delivery while keeping the saved local raw snapshot Bcc-free.

Default list views should not include BCC recipients. Use explicit detail views or audit options when BCC visibility is required.

Use an idempotency key when retrying a send command from scripts:

Terminal window
mailatlas send \
--provider gmail \
--subject "Retry-safe test" \
--text "This command can be retried." \
--idempotency-key gmail-api-test-1

If the same key already exists, MailAtlas returns the existing outbound record instead of sending a second message.

StatusMeaning
draftMessage was rendered and stored as a local draft.
dry_runMessage was rendered and stored without contacting a provider.
sendingMessage is in the process of being sent.
sentProvider accepted the send request.
queuedProvider accepted or queued the message but final delivery is not known.
errorProvider send or validation failed.
from mailatlas import MailAtlas, OutboundMessage, SendConfig
atlas = MailAtlas()
result = atlas.send_email(
OutboundMessage(
from_email="[email protected]",
subject="Build complete",
text="The build passed.",
idempotency_key="build-123",
),
SendConfig(provider="smtp", dry_run=True),
)

Check provider configuration and credentials. A dry run validates local rendering but does not verify provider authentication.

Confirm the attachment path exists and is readable from the current working directory.

Use the authenticated Gmail address or a Gmail send-as alias configured in Gmail.

If you reuse an idempotency key, MailAtlas returns the existing outbound record instead of sending again. Use a new key only when you intentionally want a new send.