How it all works · plain-language walkthrough

Mail Engine — The App Guide

A complete, end-to-end explanation of how this platform actually works — from creating a tenant to a single email landing in an inbox, and everything that happens quietly in the background to keep sending safe, reliable, and within the rules. Written for anyone who wants to understand the whole system without reading the code.

Live system · mail-engine-oci 11 Functions · 1 Autonomous Database · 1 Queue · 2 API Gateways

01 The big picture

Think of this platform as a small, self-hosted version of what services like Mailchimp or SendGrid do: it lets a business ("a tenant") register their own sending domain, upload a list of recipients, and send a real email campaign — while automatically protecting the business's sending reputation, respecting people who've unsubscribed or bounced, and never letting one tenant's mistake affect another tenant.

One-sentence mental model A campaign is validated → staged in storage → broken into a queue of individual send jobs → drained by workers that actually send the email → watched afterward for bounces and complaints that feed back into a "never email this address again" list. Everything else in this guide is either one of those five stages, or a background job that keeps the system honest when something in that chain doesn't finish cleanly.
1 · Tenant + domain setup (once per business)
Create a tenant, get an API key, register + verify a sending domain
every campaign after that
2 · Create a campaign
Upload subject/body + a recipient list — 15+ checks run before anything is queued
a new recipients.csv file appearing triggers this automatically
3 · Ingestion — turn the list into jobs
Each recipient becomes one queued "send this email" job, 2000 at a time
the queue holds jobs until a worker is free
4 · Sending
A worker builds the real email and hands it to Email Delivery to actually send
checked every 5 minutes, forever
5 · Bounce & complaint feedback loop
Anything that bounced or was reported as spam gets added to a permanent do-not-email list

02 Tenants — the businesses using this platform

A "tenant" is one customer of this platform — a business that wants to send its own email campaigns. Everything else (domains, campaigns, quotas, suppression lists) belongs to exactly one tenant, and tenants never see each other's data.

How a tenant is created

POST /tenants is the only route in the whole system that doesn't require an API key — which makes sense, since you need a tenant to exist before you can be issued one. That also makes it the one place a stranger could, in theory, create tenants without permission, so it's gated by an optional invite code: if TENANT_INVITE_CODE is configured, the request must include the matching code (checked with a timing-safe comparison, so an attacker can't guess it faster by timing failed attempts) or it's rejected outright.

On success, the platform generates a unique tenant ID and mints a first API key for it in the same call. The raw key is shown exactly once, in that response — only a one-way hash of it is ever stored. There's no "forgot my key" recovery flow, which is also why the system refuses to let you revoke your very last remaining key.

Why an invite code at all?

This tenancy's real Email Delivery sending cap is 50,000 emails/day, shared across every tenant — not per tenant. An open, ungated signup route would let anyone who found the public URL create a tenant and burn through that shared quota with no relationship to this platform's real customers.

03 Domains — proving you own what you're sending from

Before a tenant can send a single email, they have to register the domain they want to send from (e.g. rissolv.com) and prove they actually control its DNS. This is the same thing every legitimate email provider requires — it's what stops random people from sending email that claims to be from a domain they don't own.

What happens on registration

Why DKIM is mandatory but SPF technically isn't

SPF and DMARC genuinely improve deliverability and are strongly recommended, but this platform's real Email Delivery send call outright refuses to send at all without a valid, verified DKIM signature — it's not optional in practice, even though DKIM the standard is technically one of several possible authentication methods.

Verifying the domain

Adding a DNS record doesn't verify it instantly — DNS has to propagate across the internet, and then the mail provider has to actually check it. In practice this takes roughly 50 to 100 minutes for the DKIM key to go live, confirmed repeatedly across real domains. The tenant calls a /verify endpoint (safe to call more than once — it's just a status check) which reports back whether DKIM, SPF, and DMARC are each correctly configured. Only once DKIM specifically shows verified can that domain actually send mail.

04 Creating a campaign — the checkpoint before anything is sent

This is the single most guarded step in the whole system. A tenant submits a subject line, an email body, and a list of recipients (as a file upload or inline JSON) — and before any of it is accepted, it runs through a long chain of checks. Most exist to protect either the tenant (from wasting quota on addresses that will never work) or the platform's own sending reputation (from looking like a spam operation).

The validation chain, in the order it actually runs

  1. No header injection. The subject and sender name can't contain line breaks — a classic old email-spoofing trick where a malicious subject line tries to sneak in extra email headers.
  2. Attachments are sane. Each one needs a filename and must actually be valid base64 data, not garbage.
  3. The whole email fits under 10 MB once fully assembled (attachments included), which is Email Delivery's real hard limit.
  4. Any webhook/reply-to URL is safe — no pointing at internal/private network addresses (a classic server-side-request-forgery guard).
  5. Recipient list isn't absurd — capped at 100,000 per campaign.
  6. Every email address is well-formed — proper shape, sane length limits; malformed ones are set aside rather than silently dropped.
  7. Duplicates are removed (case-insensitively) so the same person never gets billed against quota — or emailed — twice from one campaign.
  8. Each domain can actually receive mail — a real DNS lookup (via MX records, falling back to A records) confirms the recipient's domain isn't a dead end. If the check itself can't get an answer in time, it assumes the domain is fine rather than blocking a real recipient over a flaky lookup.
  9. Typo domains are flagged, not blocked — e.g. gmial.com gets a "did you mean gmail.com?" suggestion, using an edit-distance match against common providers.
  10. Disposable/throwaway addresses are flagged — a known list of temporary-inbox services (Mailinator and similar).
  11. Role-based addresses are flagged — things like admin@, info@, support@ tend to have terrible engagement and higher complaint rates; flagged as a heads-up, not blocked.
  12. Already-suppressed recipients are identified up front — and cost nothing. Every recipient is checked against the do-not-email list at submission time, whatever the list size (the full address list comes back in the response for lists up to 5,000; above that, an exact count comes back instead of the list itself). Suppressed recipients are excluded from the daily-quota reservation — the platform doesn't charge quota for an email it already knows it will never send — and the console says this in plain language right after submission: "N of M recipients are on your do-not-email list and will be skipped. They don't count against your daily quota — X will send." If every valid recipient turns out to be suppressed, the campaign is rejected outright with a clear message rather than running the whole pipeline to send zero emails. The real enforcement still happens again at ingestion time regardless, so an address suppressed after submission is still caught.
  13. Content risk scan — this one actually blocks. The subject and body are scanned for the kind of thing that gets an entire sending domain flagged as spam: unrendered template placeholders ({{name}}, "Lorem ipsum," "Your Company Here"), links to known URL shorteners or placeholder domains like example.com, ALL-CAPS subject lines, excessive !!! or $$$. If anything trips, the campaign is rejected with a 400 — the tenant has to explicitly confirm they've reviewed the warning before it'll go through on resubmission.
  14. Daily sending quota. Only after everything above passes does the platform reserve quota against that tenant's daily recipient limit — an atomic, race-safe database operation, so two campaigns submitted at the same second can't both squeeze through over the limit. The reservation covers only recipients that can actually send: suppressed addresses are already excluded (see above), so a 30,000-recipient list with 10,000 suppressed reserves 20,000 — not 30,000. And a list bigger than the whole daily allowance is refused up front as one all-or-nothing decision: nothing partial ever goes out, the tenant is told to split the campaign or wait until the (UTC) day rolls over.
Why block on content risk but only flag on typos/role addresses/disposables? Content risk (placeholder text, spam-trigger phrasing) is the kind of thing that damages everyone's deliverability if it slips through — a shared sending reputation is a shared risk. Typos, disposable addresses, and role-based addresses only waste that one tenant's own quota and time, so the platform warns instead of blocking — it's the tenant's call whether they still want to send.

Once every check passes, three things get written to storage, in this exact order: the campaign's content (subject/body/attachments) first, then a database row tracking the campaign's status, and only last the recipient list file itself — because that file landing in storage is the trigger that kicks off everything in the next section. Writing it last guarantees the content it needs is already there and ready before anything starts reading it.

05 Ingestion — turning a recipient list into real send jobs

A campaign with 50,000 recipients can't just get "sent" in one step — it has to become 50,000 individual, trackable, independently-retryable jobs. That transformation is what ingestion does.

  1. Triggered automatically. The moment the recipients file lands in storage, a function wakes up to process it — no manual step, no polling.
  2. Processed 2,000 recipients at a time. Rather than trying to handle a 100,000-row file in one go (and risk a timeout losing all that work), it works through the list in fixed-size chunks, saving its exact progress (which row it's up to) after every chunk.
  3. One last suppression check happens right here. Before a job is even placed on the queue, every recipient in the chunk is checked against the do-not-email list. Suppressed addresses get marked as skipped immediately and are never queued at all — they don't even get a chance to consume a queue slot.
  4. If there's more to do, it re-triggers itself. Rather than one giant function run handling the entire list, each chunk finishes by firing off a fresh, independent run to pick up exactly where it left off — this keeps any single run short and disposable.
What if a chunk crashes or times out mid-way?

The self-triggering handoff above only fires when a chunk finishes cleanly. If the process is killed mid-chunk (a real timeout, not a clean finish), nothing automatically picks it back up — that's exactly the gap campaign_sweeper (Section 10) exists to catch, once an hour, for any campaign that's gone quiet mid-ingestion.

06 The queue, the dead-letter pile, and Connector Hub

Once a recipient becomes a "job," it sits in a queue — a waiting line — until a worker is free to actually process it. This decouples "accepting the campaign" from "actually sending every email," which is what lets the platform accept a huge campaign instantly without making the tenant wait for every single email to go out.

How the queue behaves

When a worker picks up a job, that job becomes temporarily invisible to every other worker for 3 minutes (its visibility timeout) — long enough to actually send the email. If the worker finishes successfully, the job is deleted for good. If it fails, the job silently reappears after those 3 minutes so another worker attempt can retry it.

A job doesn't get to fail forever, though — after 3 failed delivery attempts, it's automatically moved to a separate dead-letter pile instead of being retried again. That's the queue's way of saying "something is systematically wrong with this one, a human should look at it" rather than hammering a broken job forever. A dedicated alarm watches that pile and pages the team the moment even one message ends up there.

Connector Hub — the missing link between "queue" and "function"

A queue by itself doesn't know how to call code — something has to sit between the queue and the worker function, constantly asking "is there a new job?" and handing it over. That something is Connector Hub. Each individual connector can only process jobs one at a time, in sequence — so real concurrency (sending more than one email at once) comes from running multiple connectors against the same queue in parallel. This platform runs 2.

07 Sending — what actually happens to one email

This is the worker function that picks a job off the queue and turns it into a real, delivered email.

  1. Have I already handled this exact job? First check: if this recipient's job already finished (sent, suppressed, or the campaign was cancelled), stop immediately — this prevents a duplicate send if the same job gets redelivered by the queue.
  2. One last safety check. Suppressed since it was queued? Campaign cancelled since it was queued? Either one stops the send right here, as late as possible before the real network call.
  3. Rate limiting. The platform enforces a maximum sends-per-second across the whole system. If this second's "budget" is already used up, the job isn't dropped or failed — it's quietly put back on the queue as a fresh job to try again shortly, so a temporary burst never turns into a lost email or an unfair retry penalty.
  4. The real email gets built — a proper MIME message with both a plain-text and an HTML version (so it renders correctly everywhere), a one-click unsubscribe header, a physical mailing address footer (a real anti-spam/legal requirement), and every link/image in the body rewritten to route through the platform's own open/click tracking first.
  5. It's handed to Email Delivery to actually transmit — this is the one real network call to the outside world in the whole pipeline.
  6. The result is recorded — sent, or a clear failure reason, so the tenant's campaign dashboard reflects reality.
A safe way to load-test without really sending 100,000 emails There's a DRY_RUN mode that runs every single step above exactly as normal — rate limiting, suppression checks, building the message — except the actual "send it to the real world" network call is swapped for a fake success. That's what let this platform get load-tested at 100,000+ messages without spending a single message of the real account-wide daily sending cap (200/day at the time of the test; 50,000/day since the account's Pay-As-You-Go upgrade).

08 Suppression — the permanent do-not-email list

Every tenant shares one underlying principle: once an address is known to be bad — it bounced, it complained, or the person unsubscribed — the platform should never let anyone email it again, automatically, without anyone having to remember to check.

Suppression is checked twice before any email actually goes out: once during ingestion (Section 05), and again immediately before the real send (Section 07) — an address that got suppressed after a campaign was already queued still gets caught the second time. An address only needs to land on the list once, from any source, to be protected across every future campaign from every tenant that ever emailed it.

09 Bounce poller — how the platform learns something went wrong

Sending an email doesn't guarantee it arrived. Mailboxes get deleted, inboxes get full, people mark things as spam — and the platform needs to find out about all of that, even though it happens well after the original send.

Every 5 minutes, a background job (running on a small, always-on worker server) asks Email Delivery: "what new suppressions have you recorded since I last checked?" It remembers exactly where it left off (a timestamp "high-water mark"), so even if a run gets interrupted, the next run picks up from the same spot rather than missing anything. Anything it finds — a hard bounce, a soft bounce, a spam complaint, a manual block — gets written straight into the permanent suppression list from Section 08. There's no "second chance" logic (like waiting for 3 soft bounces before blocking) — a single flagged event is treated as reason enough, immediately.

Why every 5 minutes, and not instant?

Unlike some providers, this Email Delivery service doesn't push bounce notifications out in real time — the only way to find out is to ask. Polling every 5 minutes is a deliberate, honest tradeoff: suppression is never instant, but it's never more than a few minutes stale either. (It used to be hourly — the cloud's own scheduler service couldn't go finer than that, which is exactly why these jobs moved to a dedicated always-on worker server.)

On every run, this same job also checks something else: for any tenant who just got a new suppression, has that one tenant's own bounce/complaint rate over the last day crossed a real danger threshold (the same 5% bounce / 0.1% complaint levels used platform-wide)? If so, that tenant's account is automatically suspended from creating new campaigns — see Section 13 for why.

10 Campaign sweeper — the system's safety net

This is the one background job that isn't part of the "happy path" at all — its entire job is to notice when something didn't finish the way it should have, and fix it quietly.

11 Alarms & monitoring — how anyone finds out something's wrong

None of the safety nets above matter if nobody finds out when they trip. Three real alarms watch this platform continuously and page a real person the moment something crosses a line.

AlarmFires whenWhy this threshold
Hard bounce rateMore than 5% of recent sends bounced, averaged over an hourMatches the same 5% threshold major providers use to flag a sender as risky
Complaint rateMore than 0.1% of recent sends were marked as spam, averaged over an hourComplaints are rarer but far more damaging to reputation than bounces — a much tighter threshold
Dead-letter pileEven a single message has piled up in the "gave up after 3 tries" queue within a 2-hour windowAny dead-lettered message means something is systematically broken, not just an unlucky one-off

Both rate alarms are genuine percentages, not raw counts — the platform tracks exactly how many real emails were sent in the same window and divides against that, so the same alarm threshold stays meaningful whether the platform is sending 10 emails a day or 100,000.

Alarms answer "is something wrong overall?" — a separate layer (added 2026-07-16) answers "what happened to this specific email?": every send is logged twice, once when the mail service accepts it and once when the recipient's mail server takes delivery (or defers it), with the recipient address and the receiving server's actual response in each entry. That means a single soft bounce among thousands of sends is no longer an anonymous number on a dashboard — it names the exact recipient and the exact reason their server gave. Entries are kept for 30 days (the shortest retention OCI allows) and cost effectively nothing at this platform's volume, since logging is billed on data volume and each email contributes about 2 KB.

12 Object Storage & cleanup

Every campaign's content and recipient list has to live somewhere before it's processed — that's a simple file-storage service, the same idea as any cloud file bucket.

Campaign files aren't kept forever: an automatic cleanup rule permanently deletes a campaign's stored files 10 days after creation, matching how long the campaign's own database records are kept. This isn't about saving space so much as data hygiene — recipient lists are personal data, and there's no legitimate reason to keep the raw uploaded file around indefinitely once a campaign has long since finished sending.

13 Tenancy caps & fairness

Because every tenant ultimately shares the same real-world sending limits — and the same shared sending reputation — the platform enforces fairness at four different levels: three about volume, one about behavior.

14 Unsubscribe & tracking

Every sent email carries two kinds of links back to the platform: one so recipients can opt out, and two (invisible) so tenants can see engagement.

What about someone asking "what do you have on me?"

Two recipient-data rights exist side by side, and the platform implements both: a tenant can erase a recipient's send history on request (the GDPR/CCPA deletion right), and can export everything held about a recipient — their send history, whether opens/clicks were recorded, their suppression and tracking-opt-out status, and the consent basis archived for each campaign that included them (the GDPR "right of access"). Both work from the same tenant-scoped lookup, so "no results" genuinely means nothing is held. Relatedly, the "why are you allowed to email this list?" answer every campaign must provide is no longer free text — it has to be one of a fixed set of real legal bases (explicit opt-in, existing relationship, inquiry, the recipient's own transaction, or a described legitimate interest), permanently archived per campaign.

15 Why so many separate functions?

Rather than one large program doing everything, this platform is deliberately split into 11 small, single-purpose functions. That's not incidental complexity — it's a real design choice, for two concrete reasons.

  1. Blast radius. A bug in, say, the click-tracking redirect can't take down campaign creation, and a slow database query in the console's tenant list can't delay an email that's about to be sent. Each function only does one job and fails independently of the others.
  2. Scaling independently. Sending needs to run continuously and often; domain registration happens rarely per tenant. Splitting them apart means each piece can be scaled, rate-limited, or throttled on its own terms instead of one setting affecting everything at once.

16 Start to finish — one campaign's full journey

Putting every section above together, here's literally everything that happens for one real campaign sent to 3 recipients, one of whom has already unsubscribed in the past.

  1. A tenant, already onboarded with a verified domain, submits a campaign: a subject, a body, and 3 recipient addresses.
  2. The platform runs its full validation chain (Section 04) — format, dedup, MX check, suppression check, content risk scan, quota reservation. The previously-unsubscribed address is spotted right here: the response (and the console banner) says 1 of 3 recipients will be skipped, and quota is reserved for only the 2 that can actually send. The campaign's content and recipient file are written to storage.
  3. The recipient file landing in storage automatically wakes up ingestion (Section 05), which re-checks all 3 addresses against the suppression list — catching anything suppressed in the moments since submission. The unsubscribed address is marked SUPPRESSED right there and never queued. The other 2 become real jobs on the queue.
  4. Connector Hub (Section 06) notices the 2 new jobs and hands them to the sending function.
  5. The sending worker (Section 07) re-checks suppression (still clean), builds the real email with tracking links and an unsubscribe header, and hands it to Email Delivery. Both emails go out successfully.
  6. A day later, one of the two delivered emails hard-bounces (the mailbox no longer exists). Within minutes, bounce_poller (Section 09) notices it on its routine check and adds that address to the suppression list — permanently, for every tenant, going forward.
  7. Meanwhile, campaign_sweeper (Section 10) has been quietly running every 5 minutes the entire time, confirming this campaign never stalled and sweeping up nothing, since nothing here needed recovering.
  8. The whole time, Monitoring (Section 11) tracked this campaign's contribution to the platform-wide bounce rate — one bounce out of two real sends is a 50% rate for this tiny example, but in real volume, that's exactly the percentage the alarms are watching for.

17 Keeping the platform itself tidy

Not everything here is about handling tenant traffic — a small amount of ongoing housekeeping keeps the platform's own infrastructure from quietly growing unbounded in the background.

Every time new code ships, each of the platform's 11 functions gets rebuilt into a fresh container image. Only the newest image is ever actually used — but the previous one doesn't automatically get deleted when that happens, it just sits there, unused, taking up storage. Left alone, that adds up: after around 90 real deploys, the image registry had accumulated roughly 3,400 old, unused images across the platform (about 72 GB). Since a deploy happens every time code merges, this was quietly growing forever with nothing cleaning it up.

The fix is simple: right after every deploy, the pipeline now automatically deletes everything except the 10 most recent images for each function — enough to still roll back to a recent version if ever needed, without keeping years of history around for no reason. The very first time this ran, it cleared out the entire backlog in one pass; from now on, it just keeps things tidy on every future deploy without anyone having to think about it.