Skip to main content

VibeMail SDK for Python

Official SDK for the VibeMail transactional email API. Python 3.9+, standard library only, installing it pulls in nothing else.

pip install vibemail

Send an email

import os
from vibemail import VibeMail

vibemail = VibeMail(os.environ["VIBEMAIL_API_KEY"])

result = vibemail.send(
    from_address="hello@yourdomain.com",
    to="ada@example.com",
    subject="Welcome",
    html="<p>Glad you're here.</p>",
    text="Glad you're here.",
)
print(result["id"])

send() returns once the API has accepted the message, not once it has been delivered. Use get(id) to follow it.

Always send a text alternative alongside html. Mail with no plain-text part is measurably more likely to be spam-foldered.

from is a reserved word in Python, so the argument is spelled from_address; it goes out as from on the wire.

Recipients

One message goes to one recipient. To reach several people, use batch(), it sends a separate message to each, which is also what stops your recipients from seeing one another's addresses:

result = vibemail.batch([
    {"to": "ada@example.com", "subject": "Welcome", "text": "Hi Ada"},
    {"to": "alan@example.com", "subject": "Welcome", "text": "Hi Alan"},
])

for entry in result["data"]:
    if "error" in entry:
        print("rejected:", entry["error"])

Each entry succeeds or fails on its own; one bad address does not sink the rest.

Passing several addresses to to raises ValueError rather than quietly sending to the first.

Retries and idempotency

Pass idempotency_key on anything you might retry. A repeat carrying the same key returns the original result instead of sending a second copy, which matters because a client that times out has no way of knowing whether the message went out.

vibemail.send(
    to="ada@example.com",
    subject="Receipt",
    text="Thanks!",
    idempotency_key=f"receipt-{order_id}",
)

The client retries 429 and 5xx responses on its own (twice by default, honouring Retry-After) and never retries a request the server rejected on its merits. Pass max_retries=0 to handle that yourself.

Scheduling

from datetime import datetime, timedelta, timezone

result = vibemail.send(
    to="ada@example.com",
    subject="Reminder",
    text="Standup in 15 minutes.",
    scheduled_at=datetime.now(timezone.utc) + timedelta(hours=2),   # or "in 2 hours"
)

vibemail.cancel(result["id"])   # while it is still scheduled

Up to 30 days ahead. A naive datetime is read as UTC rather than as the server's local time. Once the dispatcher has claimed a message it is on its way out and can no longer be withdrawn.

Templates

templates = vibemail.list_templates()

vibemail.send(
    to="ada@example.com",
    template="welcome",
    variables={"name": "Ada"},
)

Anything you pass explicitly - subject, html, text, overrides the stored template, so you can vary one part without redefining the rest.

Domains

Mail sent from your own domain needs that domain added and verified. Adding one returns the DNS records to publish; it stays unverified until they resolve.

domain = vibemail.create_domain("yourdomain.com")

for purpose, record in domain.get("dns_records", {}).items():
    print(purpose, record["type"], record["host"], record["value"])
# verification TXT _vibemail-verify.yourdomain.com vm-verify-...
# mx           MX  yourdomain.com                 mail.vibemail.ai
# spf          TXT yourdomain.com                 v=spf1 include:mail.vibemail.ai -all
# dmarc        TXT _dmarc.yourdomain.com          v=DMARC1; p=quarantine; ...

dns_records is keyed by purpose, not a list: verification, mx, spf and dmarc. Each carries type, host, value, and priority where the type takes one. Publish all four; the domain verifies once they resolve.

Come back for the same records at any time, along with whether verification has gone through:

d = vibemail.get_domain(domain["id"])
print(d["is_verified"])

Listing is paged. total counts every domain on the account, not just the page you asked for:

page = vibemail.list_domains(limit=25, offset=0)
print(page["total"], len(page["data"]))

vibemail.delete_domain(domain["id"])

Removing a domain does not affect mail already sent from it.

Contacts

vibemail.create_contact(
    "ada@example.com",
    name="Ada Lovelace",
    notes="met at the analytical engine demo",
)

page = vibemail.list_contacts(search="ada", limit=50)
vibemail.delete_contact(page["data"][0]["id"])

search matches against address and name.

Suppressions

Addresses that will not be sent to: hard bounces, spam complaints, and anything blocked by hand. A send to a suppressed address is dropped rather than attempted, which is what keeps a bad list from taking your sending reputation with it.

page = vibemail.list_suppressions(limit=100)

for entry in page["data"]:
    print(entry["email"], entry["reason"])   # "manual", or "hard bounce: ..."

reason is free text, not a fixed set: manual for anything added by hand or through the API, and hard bounce: ... carrying the remote server's own wording when the queue gave up on an address.

Paging

list_domains, list_contacts and list_suppressions all return the same envelope:

{"object": "list", "total": 128, "limit": 50, "offset": 0, "data": [...]}

limit defaults to 50 and is capped at 100. Walk the whole set by stepping offset until you have total:

everything = []
offset = 0
while True:
    page = vibemail.list_contacts(limit=100, offset=offset)
    everything.extend(page["data"])
    if len(everything) >= page["total"]:
        break
    offset += 100

Errors

from vibemail import VibeMail, VibeMailError, VibeMailTimeoutError

try:
    vibemail.send(to="ada@example.com", subject="Hi", text="...")
except VibeMailError as err:
    print(err.status, err.detail)     # 422, "'to' and 'subject' are required"
    if err.is_retryable:              # 429 or 5xx
        ...
except VibeMailTimeoutError:
    ...                               # exceeded `timeout`, default 30s
Status Means
400 The request was malformed, such as a domain that is not a domain.
401 The API key is missing, wrong, or revoked.
402 A plan limit was reached. err.detail says which. Not retried.
404 No such record, or not yours.
409 Already exists, such as a domain someone has registered.
422 Required fields missing, or a schedule more than 30 days out.
429 Rate limited. Retried automatically, honouring Retry-After.
5xx Our fault. Retried automatically.

Options

vibemail = VibeMail(
    "vm_live_...",
    base_url="https://vibemail.ai",
    timeout=30.0,
    max_retries=2,
)
Option Default
api_key Required, positional.
base_url https://vibemail.ai Point at a self-hosted deployment.
timeout 30.0 Per-request, in seconds.
max_retries 2 Applies to 429 and 5xx only.

API

send(**fields) Send one message.
batch(emails, idempotency_key=None) Send many, one recipient each.
get(email_id) Status of a send.
cancel(email_id) Withdraw a scheduled send.
list_templates() Stored templates.
list_domains(limit=None, offset=None) A page of sending domains.
get_domain(domain_id) One domain, with the records that verify it.
create_domain(domain) Add a domain.
delete_domain(domain_id) Remove a domain.
list_contacts(search=None, limit=None, offset=None) A page of contacts.
create_contact(email, name=None, notes=None) Store a contact.
delete_contact(contact_id) Forget a contact.
list_suppressions(limit=None, offset=None) Addresses that will not be sent to.

Send fields

Field
to Recipient. One per message; use batch() for many.
from_address Sender. Must be an address your account owns. Defaults to the account address.
subject
text Plain-text body. Always send one alongside html.
html HTML body.
template Name or id of a stored template.
variables Values substituted into the template.
tags Labels carried through to analytics.
track_opens Injects a tracking pixel into HTML sends. Defaults to on.
track_clicks Rewrites links through the redirector. Defaults to on.
scheduled_at ISO 8601, a datetime, or a relative offset like "in 2 hours". Up to 30 days.
idempotency_key A retry with the same key returns the original result.

A naive datetime is treated as UTC. An aware one is converted, not relabelled.

Not in the SDK yet

Webhooks and analytics are served by the API but are not wrapped here. They will be added when the shapes settle; until then reach them with urllib or requests and the same bearer token.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vibemail-1.2.0.tar.gz (13.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vibemail-1.2.0-py3-none-any.whl (10.1 kB view details)

Uploaded Python 3

File details

Details for the file vibemail-1.2.0.tar.gz.

File metadata

  • Download URL: vibemail-1.2.0.tar.gz
  • Upload date:
  • Size: 13.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for vibemail-1.2.0.tar.gz
Algorithm Hash digest
SHA256 246cda26a5326754e279454ee80eb7359d7bd4889dc818adb3d7e5ca87a4b321
MD5 bec88a5c57fa14eae7d6beb7ecbf61ed
BLAKE2b-256 926984bd5bcf7a781679b68480e58a812df8fc928d34e171dab4397ca04aa640

See more details on using hashes here.

Provenance

The following attestation bundles were made for vibemail-1.2.0.tar.gz:

Publisher: publish.yml on vibemailai/vibemail-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vibemail-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: vibemail-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 10.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for vibemail-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 77674e54aedef9e048811635cf198edde4d8f411afa13330a74027a6797f834d
MD5 47a2f95ef15631b63a6e8e15f198edff
BLAKE2b-256 108f7a45c36052e086ca7b0979539b6b3f5fd1b90f4c5f0ae7f929a7d6711774

See more details on using hashes here.

Provenance

The following attestation bundles were made for vibemail-1.2.0-py3-none-any.whl:

Publisher: publish.yml on vibemailai/vibemail-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page