Skip to main content

Shared enums, Resend provider, voyage matching, and optional PostgreSQL email helpers

Project description

wr-common-lib

Shared enums, Resend provider, inbound voyage matching helpers, and optional PostgreSQL write helpers for the email table.

PyPI wr-common-lib
import wr_common_lib

Requires Python 3.12+.

Install

pip install wr-common-lib
pip install "wr-common-lib[resend]"   # ResendProvider (httpx)
pip install "wr-common-lib[db]"       # EmailDbOper (async-db-tools)
pip install "wr-common-lib[db,resend]"

Package layout

src/wr_common_lib/
├── __init__.py          # __version__
└── email/
    ├── __init__.py      # public exports
    ├── constants.py     # MailFlow, MailStatus, VoyageStatus, PassVia, CustomerRole
    ├── encoding.py      # file_to_base64, file_to_attachment
    ├── provider.py      # ResendProvider, SendResult, ReceivedEmail
    └── db_oper.py       # EmailDbOper, get_email_content_hash

Enums

Aligned with PostgreSQL enum types.

from wr_common_lib.email import (
    MailFlow,
    MailStatus,
    VoyageStatus,
    PassVia,
    CustomerRole,
)

MailFlow.INBOUND
MailStatus.PENDING.value
VoyageStatus.UNDERWAY
PassVia.SUE
CustomerRole.OWNER
Python PostgreSQL
MailFlow mail_flow
MailStatus mail_status
VoyageStatus voyage_status
PassVia pass_via_enum
CustomerRole customer_role_enum

Mail status flows

Outbound:  PENDING → SENT | FAILED;webhook → DELIVERED | BOUNCED
Inbound:   RECEIVED → PARSED | PARSE_FAILED

Content hash

from wr_common_lib.email import get_email_content_hash

content_hash = get_email_content_hash(
    to="a@example.com,b@example.com",
    subject="...",
    content="...",
    cc="",
    attachments=[{"filename": "report.pdf"}],
)

Normalizes to / cc (comma-separated), strips subject/content, and hashes attachment filenames only (not file bytes).

Inbound parsing helpers

from wr_common_lib.email import extract_ship_candidates, extract_imo_from_content

extract_ship_candidates("MV OCEAN STAR - noon report")
extract_imo_from_content("IMO 9682930")   # "9682930" or None

Attachment encoding

Converts local files to Base64 for Resend attachments[].content.

from wr_common_lib.email import file_to_base64, file_to_attachment

b64 = file_to_base64("/path/to/report.pdf")

att = file_to_attachment("/path/to/report.pdf")
# {"filename": "report.pdf", "content": "<base64>", "content_type": "application/pdf"}

ResendProvider

Requires httpx ([resend] extra). Uses the Resend API.

from wr_common_lib.email import EmailProvider, ResendProvider, file_to_attachment

provider = ResendProvider(
    api_key="re_...",
    default_from="noreply@example.com",
    default_from_name="My App",
)

# Send (attachments from file_to_attachment)
result = await provider.send(
    to="user@example.com",
    cc="",
    subject="Hello",
    content="Plain text body",
    attachments=[file_to_attachment("/path/to/file.pdf")],
    mail_from=None,           # optional override
    email_id=email_id,        # optional, sets X-Email-ID header
)
# result.ok, result.message_id

# Inbound (Receiving API)
mail = await provider.received(message_id)
# mail.ok, mail.subject, mail.text, mail.html, mail.from_addr, mail.to, mail.cc, ...
body = mail.content   # text, else html

EmailDbOper

Requires the [db] extra. Pass an async-db-tools PostgresPool (or compatible pool with fetchval / execute).

from wr_common_lib.email import EmailDbOper, MailFlow, MailStatus, get_email_content_hash

db_oper = EmailDbOper(pool)

Write operations only; reads should use db_oper._db directly (e.g. fetchrow, fetchval).

insert_email

email_id, created = await db_oper.insert_email(
    task,
    MailFlow.OUTBOUND,
    MailStatus.PENDING,
)
# created is True when a new row was inserted, False when content_hash already existed

Task fields (inbound and outbound use the same shape):

Field Required Notes
imo yes
voyage_id yes UUID string
mail_from yes Sender
to yes Recipients (comma-separated allowed)
cc no Default ""
subject no Default ""
content no Default ""
attachments no List of dicts with filename
content_hash no Computed via get_email_content_hash when omitted
created_user_id no Outbound only

On duplicate content_hash, the insert is skipped (ON CONFLICT DO NOTHING); the existing row id is returned and created is False.

update_status / update_parsed_data

await db_oper.update_status(email_id, MailStatus.SENT)
await db_oper.update_parsed_data(email_id, {"parsed": "..."})

get_imo_and_voyage_id

Resolves (imo, voyage_id) for inbound mail when exactly one active voyage matches (READY / UNDERWAY):

  1. mail_from in voyage.ship_emails
  2. Vessel name from subject (extract_ship_candidates)
  3. IMO in body (extract_imo_from_content)

Returns (None, None) if ambiguous or not found.

imo, voyage_id = await db_oper.get_imo_and_voyage_id(
    mail_from="ship@example.com",
    subject="MV OCEAN STAR report",
    content="IMO 9682930 ...",
)

Dependencies

install brings in
wr-common-lib enums, encoding, hash/parsing helpers, ResendProvider (needs httpx — use [resend])
wr-common-lib[db] async-db-tools, EmailDbOper, get_imo_and_voyage_id
wr-common-lib[resend] httpx

Your application owns API keys, database URL, and pool lifecycle.

Publish

pip install -e ".[db,resend]"

# 1. Update CHANGELOG.md for the new version
# 2. Bump version and publish
uv version 0.1.10
export UV_PUBLISH_TOKEN=pypi-...
uv build && uv publish

git tag v0.1.10 && git push --tags

PyPI does not allow re-uploading the same version.

License

MIT — see LICENSE.

Project details


Download files

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

Source Distribution

wr_common_lib-0.1.9.tar.gz (9.5 kB view details)

Uploaded Source

Built Distribution

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

wr_common_lib-0.1.9-py3-none-any.whl (10.9 kB view details)

Uploaded Python 3

File details

Details for the file wr_common_lib-0.1.9.tar.gz.

File metadata

  • Download URL: wr_common_lib-0.1.9.tar.gz
  • Upload date:
  • Size: 9.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for wr_common_lib-0.1.9.tar.gz
Algorithm Hash digest
SHA256 1718848e4783a0c87b1735e5cb5db9e5fda5db624809ebcfd56fe7d05141d2ef
MD5 71f1929c419821a0ec38c4f0fa2b1ba9
BLAKE2b-256 4652f974cbeae8a6fd85091fd04d526c415cc3ec4db9182e45b92952dc6f7e4e

See more details on using hashes here.

File details

Details for the file wr_common_lib-0.1.9-py3-none-any.whl.

File metadata

  • Download URL: wr_common_lib-0.1.9-py3-none-any.whl
  • Upload date:
  • Size: 10.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for wr_common_lib-0.1.9-py3-none-any.whl
Algorithm Hash digest
SHA256 0f23879b70b37242b7ceccffd18894d6c370e8bbf5be295c53c2bd402239c6e2
MD5 70c150b48cc19e41ddb4103381a59f99
BLAKE2b-256 e6f656c922f7ad6c699cf1d0941f31c2777c12d022c917c2b22875d27fe5966a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page