Transfer Your Dyn Email Delivery

Dyn Email Delivery (and the Dyn business) are reaching end-of-life after May 31, 2023. Use this guide to get acquainted with and migrate to its replacement, the Email Delivery service of Oracle Cloud Infrastructure (OCI).

Overview

Service Feature Parity

OCI Email has most of the features of Dyn Email, but there are a few key ones in final development and nearing release in the coming weeks. In addition to reputable email delivery, OCI currently offers other core features such as bounce and complaint collection, email authentication standards, approved (wildcard) domains, deliverability performance including possible dedicated IP resources, detailed and summary activity reporting, and a host of other features offered through tight integration with other Oracle Cloud services.

As of this writing, we are working on features such as open and click engagement tracking, HTTPS API sending, custom headers, and custom return path among others. Some of these features have already reached limited availability state; please reach out to us if you need these features or wish to gain early access.

For more information on availability and timeline, or to gain early access when available, please reach out to migrations@dyn.com.

Migration Help

Depending on your use cases and level of integration, there may be a lot of pieces to consider. If you have questions about a certain feature or need guidance on how to map your current Dyn Email integration over to Oracle Cloud, please reach out to us. While we cannot migrate certain data over (SMTP and web portal passwords, user permissions, and others), we do have tools and guidance we can provide for migration of approved senders and/or suppression lists (see below). Reach out to migrations@dyn.com for help.

Approved Sender / Email Domain Migration Tool
The OCI Email Delivery service team can provide a relatively simple Python script which reads your approved senders in Dyn Email Delivery and create the matching email domains and approved senders in Oracle Cloud. Contact us for more information.

Important Note on Suppression List Migration
Suppression lists exist to help maintain good email sender reputation by preventing invalid or unwilling email addresses from being repeatedly emailed. Both Dyn and Oracle Cloud charge for emails submitted to recipients that are suppressed, even though the emails are not sent. For this reason especially, and to keep a focus on good sender reputation, we highly recommend good email and suppression list hygiene; that is:

  1. Maintain a close eye on updates to the suppression list
  2. Promptly update your own system and email list(s) to reflect the update (such as removing or fixing the recipient address)
  3. Remove recipients from the suppression list once you’ve updated your own list(s)

This way, you save on costs associated with submitting email for suppressed recipients, and your email sends are more optimized and targeted only to recipients who exist and want emails.

You can safely skip migrating suppression lists if you've been practicing at least steps 1 + 2 above; that is, updating your own email lists to reflect updates to the suppression list. Otherwise, we strongly recommend reviewing your current Dyn suppression list(s) to make sure the addresses there are updated/removed in your own lists; only then would it be ok to skip suppression list migration. For those who must migrate their suppression list, the migration tool mentioned above can do this; but again, this is not ideal from the perspective of cost and list hygiene.

Oracle Cloud Email Delivery Quickstart

We recommend the following steps to get up and running toward your first email send.

Step 1: Create Oracle Cloud Account and Credentials

Key documentation links for reference:

Create an Oracle Cloud Infrastructure (OCI) account

Oracle Cloud Infrastructure requires an account (“tenancy”) be created to support setting up cloud services such as Email Delivery. In addition, because OCI utilizes a different means for authenticating web and SMTP users, these accounts must be configured fresh there. Existing Dyn credentials cannot be transferred from Dyn to OCI.

If you do not yet have an account, you can start with a free account. Oracle Cloud’s Free Tier includes a practical amount of infrastructure and services to get started, including allotment to send up to 100 emails a day to test out the service. You will have to enter a credit card, but it is not billed.

Before setting up Email Delivery, consider how best to logically separate your email configuration and activity, if needed. Dyn Email Delivery uses sub-accounts to segment email sending and reporting, and helps in controlling access. OCI uses “compartments” to do this, allowing you to organize and isolate your cloud resources such as approved senders. For more information and to create Oracle Cloud Infrastructure compartments, see Managing Compartments.

Set up Users and Groups

Unlike Dyn Email Delivery, Oracle Cloud Infrastructure provides a robust set of permission features that allow you to set up granular permission policies to manage access to various elements of the Email Delivery service, including sending and approved sender creation permissions. For more information, see Overview of Oracle Cloud Infrastructure Identity and Access Management. This includes the recommended use of Groups to help organize with similar access, and grant them appropriate access to manage resources.

Once you have your tenancy created, you have the option to set things up manually using the OCI web console, or you can look at scripts provided here to utilize Oracle Resource Manager and the Terraform CLI to automate setup and management.

Generate SMTP Credentials

SMTP credentials are added to a User within your tenancy for use in submitting emails to OCI for delivery. Like Dyn, credentials used to authenticate into the OCI web console are different than those used for email submission.

  1. Open the navigation menu. Under Identity and Security, click Users.
  2. Either create a user for dedicated use in submitting email, or locate an existing user in the list. Click their name to view details.
  3. Under Resources in the left sidebar, click SMTP Credentials.
  4. Click Generate SMTP Credentials.
  5. Enter a Description of the SMTP Credentials in the dialog box.
  6. Click Generate SMTP Credentials. A user name and secure password are auto-generated and displayed.
  7. Copy the username and password for your records and click Close.

If you wish, you can disable capabilities for the user(s) with SMTP credentials for security purposes. Click the Edit User Capabilities button on user details page.

Step 2: Setup Email Delivery Service

Oracle Cloud offers a robust set of services and security control options to support even the most complex requirements. However, you can get started for reputable sending relatively quickly.

Below, we offer condensed steps for a minimal setup in OCI Email Delivery. You can also consult the Getting Started with Email Delivery guide for more detail.

A word about regions: Oracle Cloud is a region-based service, unlike Dyn Email Delivery which has a single set of global endpoints. You will want to consider which region(s) of Oracle Cloud to set up in, as each region requires its own setup. For now, you can just pick the region closest to where you are to get started.

The ideal way to think about this: where do your users reside in the world and are there restrictions within those countries as relates to where email comes from? If you have an Australian-based arm of your company, you could set up a region there and another one for U.S-based affairs, etc.

Minimum Requirements for OCI Email Delivery Configuration

  • SMTP Credentials
  • Policy to allow Email Delivery configuration
  • Email Domain(s)
  • DKIM + SPF (highly recommended)
  • Approved Sender(s)
  • Suppression List, if applicable

Add IAM Service Policy to Allow Email Delivery Configuration

OCI uses Identity and Access Management (IAM) policies to help control access to cloud resource management. Email Delivery offers a single policy or more granular policies, depending on your needs. To enable all operations on all email resources for a specific user group, use the following policy:

Allow group [Your Group Name] to manage email-family in tenancy

There are other policies you will want in place, especially when accessing logs and managing suppressions. See Creating User Permissions in the OCI Email Delivery documentation for more detail.

Step 3: Migrate Email Domains and Approved Senders

Before migrating approved senders, we recommend assessing what you have set up in Dyn. If you have inactive or abandoned approved senders, we recommend cleaning these or at least not bringing them over to OCI. If you used an approved sender address five years ago, but no longer need it, you would not need to include that for OCI.

Reporting data will remain in Dyn Email and cannot be migrated. Dyn Email will remain available beyond the end-of-life date for a short time, only to allow for report retrieval.

Add Email Sending Domain(s)

OCI uses Email Domains to centralize configuration and reporting and make it easier to set up important, industry-standard authentication measures like SPF and DKIM. For more information, see Managing Email Domains.

For each of the domains you will send email from:

    Search the navigation system for “Email Delivery”, or open the navigation menu and click Developer Services, then click Email Delivery. In the Email Delivery sidebar menu, click Email Domains.
  1. Ensure that you are in the correct compartment (under List scope in the left sidebar). Your user must be in a group with permissions to manage email domains in this compartment.
  2. Click Create Email Domain.
  3. Enter your email sending domain name, the one you plan to use for your “From” email address. This should be a domain you own or control in DNS because measures used to establish verification and authentication require a DNS record or similar actions.
  4. Click Create Email Domain. The new domain will appear on its new detail page.
  5. We recommend enabling logs for all sending domains. Under Email Logs, click Logs.
  6. Enable Outbound Accepted and Outbound Relayed logs by enabling the Enabled sliders for each.

If you have a lot of approved senders and/or sending domains, we can provide a Python script which copies your approved senders over and creates all the respective email domains. Please reach out to migrations@dyn.com for this option.

Set up DKIM for your Email Domain(s)

DKIM (DomainKeys Identified Mail) is an important step to ensure email you send reaches the inbox and is considered reputable. It utilizes DNS records to help achieve cryptographic signing of emails, ensuring the email is seen by inbox providers as genuine.

We highly recommend setting up DKIM fresh with new signing keys especially if you are currently using less secure 1024-bit or less encryption strength; OCI Email Delivery defaults to using strong, 2048-bit encryption. OCI also encourages the use of CNAME records for setting up DKIM (in contrast to the TXT record method used in Dyn), which allows for easier, seamless key rotation.

To set up fresh DKIM manually, see the guide to Setting up an Email Domain with DKIM for helpful steps, including links to documentation for several popular DNS providers. While you can attempt to send some test mail without DKIM set up, we highly recommend setting up DKIM right away to avoid delivery issues later. In addition, certain features require DKIM be set up as well (such as wildcard approved senders and custom headers).

Configure SPF

SPF (Sender Policy Framework) is an important, industry-standard measure to let email inbox providers know that OCI Email Delivery is allowed to deliver emails on your behalf. Like DKIM, SPF uses DNS records. See the Configuring SPF guide for steps to set up this important permission. Note that SPF is set up by region, so you would want to use the includes for the specific region(s) you are sending from.

If you have a lot of sending domains that you do not own/control, adding a new SPF include in DNS can be a big challenge. To make this easier, we have added the OCI Email Delivery US-based SPF IP ranges to the Dyn SPF; this means you will not have to update DNS for SPF as long as you currently have the Dyn SPF added. However, we strongly recommend working to replace the Dyn SPF for the OCI Email SPF for each domain, to benefit from any changes to the OCI Email SPF.

Add Approved Senders to Your Account

Like Dyn Email Delivery, OCI uses Approved Senders to help ensure good email delivery practices, segment sending activity and reporting, and strengthen use of other features. Each “From” address you plan to use must be added as an Approved Sender. See the guide to Creating an Approved Sender for more helpful information and steps.

Instead of adding Approved Senders manually, you can also use a host of Developer Tools and Resources including Terraform, SDKs, and the OCI CLI to programmatically add senders. This can be combined with the Dyn Email Delivery Approved Senders APIs to perform a seamless migration of all senders.

Step 4: Start Sending!

With your SMTP credentials, email domains, important authentication measures, and approved senders in place, you can start sending. See the guide to Configuring SMTP Connection for info on the region-based connection endpoints and TLS requirements, and Sending an Email for a review of the important setup pieces and links to API and SDK documentation.

Aside from programmatic ways to send email, you can also use tools such as swaks to do very basic send tests using your new configuration.

To check the results of your sending:

  1. Login to the OCI web console.
  2. Open the navigation menu and click Observability and Management. Under Logging, click Search.
  3. In the Custom Filters, enter a filter to use, such as the following to search for a particular recipient:
    data.recipient = testing@company.com
  4. Assuming you enabled logs for the email sending domain earlier, click within Select logs to search and select the log(s) to search (outboundaccepted and outboundrelayed)
  5. Change the Filter by time as needed to ensure the window of time includes your email send test
  6. Click Search. For each email that was successfully delivered you should see two entries: one for the “Accepted” action (OCI Email accepted the email submission), and another for “Relayed” action (the recipient email provider accepted the mail for delivery).

For an introduction to more detailed information on logs and count data (metrics), see the Reporting and Reporting API Quickstart below.

Step 5: Analyze Suppression and Email Lists for Cleanliness

Email Suppression Lists are important to ensure good delivery reputation. You do not need to add suppressions in order to start sending, but before fully moving over we recommend analyzing your current Dyn suppression lists for cleanliness and good email sending reputation.

Depending on your sending practice and list hygiene, you may have a large suppression list built up over time. Ideally, suppression list recipients should only remain on the list for a short time to allow for you to see the update, remove (or fix) the recipient in your own system, then remove the recipient from the suppression list.

If you have kept up with additions to the suppression list and removed or fixed these recipients in your own lists as you go, you can skip migrating the suppression lists since your email list(s) should already be clean. Otherwise, we highly recommend reviewing your suppression list and removing/fixing the recipients in your own lists so you can avoid migrating. This way, you save on email submission costs and have a cleaner start with Oracle Cloud Email Delivery. (Like Dyn Email, Oracle Cloud charges for each email submitted for delivery even if the email recipient is on the suppression list.)

Cancelling Dyn Accounts

Cancel Dyn Email Delivery Service (enterprise accounts)

You can cancel your Dyn Email Delivery enterprise account by emailing customersuccess@dyn.com. Note this is not done automatically once you start sending through OCI Email Delivery. Once you are fully done with Dyn Email Delivery, let us know via that email address ASAP.

Cancel Dyn Email Delivery Express Service

After you have fully moved over to OCI Email Delivery and are ready to cancel your Dyn Email Delivery Express service account, login to your account at account.dyn.com with your eCommerce username and password.

  1. Under My account, click My Services.
  2. Next to Email Delivery Express, click View.
  3. Click Request Cancellation

OCI Email Feature Mapping and Documentation

This table lists out the main features of Dyn Email, their OCI Email counterparts, and links to documentation were applicable.

Configuring DKIM for OCI Email (done at the Email Domain level)

Area Dyn Email Feature OCI Email Feature Notes
Account Structure and Settings Master and sub-account structure

Oracle Cloud is a full-featured global cloud with multiple options depending on your requirements:

  • parent + child tenancies (more flexible)
  • compartments within a single tenancy

Read the Overview of Identity and Access Management for a good introduction to Oracle Cloud accounts and organization/access strategies.

 
Suppression List
(One per sub-account)
Suppression Lists in OCI Email
(One per tenancy, always set up in root compartment)
 
X-Headers

Custom Headers feature

Note: Only email domain log (detail) data supports use of filtering by custom header; metric (count) data does not support custom header filtering

Custom Headers currently only managed via API or support ticket request; web console availability coming soon
Postbacks (bounce, complaint, unsubscribe only)

Achievable with mix of OCI VCN/subnet, Service Connector, and Functions services. Optionally can integrate the Notifications service but would need Functions in order to pass key log data through and include it in postback/webhook requests.

In short, a service connector can be created to key off an Email Domain Log search using whatever field criteria you wish, and then call a function with the log result to send that data out. See Postback Setup below for steps to configure this.

 
Approved Senders Approved Senders

Approved Senders in OCI Email

Also: OCI Email introduces the concept of Email Domains which allow for more centralized setup of sender domain-level security and features such as SPF, DKIM, and metrics (count) and log (detail) gathering

Can be set up in root or other compartment

OCI Email does not allow certain public mailbox service provider email addresses to be added as approved senders (such as gmail.com, hotmail.com, yahoo.com)

Wildcard Senders (*@domain.com) Enter address as "@domain.com" (no asterisk) Domain must have DKIM set up and active in order for sender to be created
SPF

Configuring SPF for OCI Email

 
DKIM OCI Email strongly recommends use of CNAME records in DNS to set up DKIM, rather than TXT records used in Dyn. This provides for easier rotation of DKIM keys.  
Web Interface Web portal (https://email.dyn.com) OCI web console (https://www.oracle.com/cloud/sign-in.html?redirect_uri=https%3A%2F%2Fcloud.oracle.com%2F) — docs Dyn ID credentials not transferable; OCI console supports SSO/federated login
API REST API

OCI Developer Tools (includes CLI, SDKs, REST APIs, DevOps tools like Terraform Provider and Resource Manager)

Also see the OCI Email Terraform Quickstart

See below for table listing each Dyn Email API method, with mapping to appropriate OCI API

SDKs exist for Java, Python, TypeScript/JavaScript, .NET, Go, Ruby, and PL/SQL
Email Submission + Delivery (SMTP + API) SMTP submission endpoint (smtp.email.dynect.net, plus some customer-specific ones) OCI Email SMTP regional endpoints OCI Email SMTP cipher support is more limited but more secure
Send API HTTPS Email submission in limited availability, contact us for details only HTTPS will be supported
List-Unsubscribe Multiple options:
  • include unsubscribe link in your email content (requires customer to handle these requests)
  • List-Unsubscribe (coming June 2023)
    • if not set, OCI will set this header and handle all requests (adding recipient to suppression list for all future emails
    • alternatively, header can be set by customer to route requests back to them for more specific,nuanced handling (such as more selective opting out)
 
"Blackhole" domain for bulk testing (dyn-blackhole.com) Use discard.oracle.com for testing, to have recipient emails discarded at outbound delivery and not delivered to Internet  
Dedicated IPs/Pools Dedicated IP Addresses feature, available via support ticket, applicable at email domain or sender level  
Returns (Bounces, Spam Complaints, Unsubscribes) Custom Return Path, applicable only to entire customer account (at "master" account level)

Custom Return Path feature, available via support ticket, applicable at approved sender level

The Return-Path address is located in every outbound message's header. By default, we use our own Return-Path address for all customers. Similar to Dyn Email, OCI Email offers the ability to customize this for a few reasons:

  • Reputation
  • Most effective allowlisting
  • DMARC (SPF) alignment

We strongly recommend this feature when migrating your Dyn account to OCI Email Delivery. OCI Email can match any approved sender with their own corresponding custom Return-Path (Dyn could only support one for each customer account). Taking advantage of this feature will greatly help your domains deliverability. Read more information and steps to get a custom Return-Path set up in OCI.

 
Engagement Tracking Open Tracking Now in limited availability, contact us for details  
Click Tracking Now in limited availability, contact us for details  
Custom Tracking Domains Under development, contact us for timeline details  
Reporting / Analytics Sent Report

Counts: "EmailsAccepted" metric — see Email Delivery metrics

Detail records: email domain "outboundaccepted" logs with "data.action = accept"; logs must be enabled on per-email domain basis first

See Reporting Quickstart section

Delivered Report

Counts: "EmailsRelayed" metric — see Email Delivery metrics

Detail records: email domain "outboundrelayed" logs with "data.action = relay"; logs must be enabled on per-email domain basis first

See Reporting Quickstart section

"Relayed" in this case means message was successfully routed to the recipient email provider; see note above under "Reporting"

Blocked Report

Counts: "EmailsSuppressed" metric — see Email Delivery metrics

Detail records: email domain "outboundaccepted" logs with "data.action = accept" and "data.errorType = "Recipient suppressed"; logs must be enabled on per-email domain basis first

Note: the "EmailsBlocklist" metric in OCI is not the same as the Dyn "Blocked" report. Blocklist metrics show up when an email wasn't delivered (bounced) because of one of a particular set of reasons having to do with the delivery IP address being on a third-party IP blocklist. These are quite rare, enough that we don't yet even document them; but we do intend to in the future, as the set of reasons is something we can update as new ones come up (which, again, is very rare). These reasons do not involve the recipient address being blocked or on a suppression list -- it's all about whether an IP on an OCI Email outbound delivery server is on a blocklist. Our Deliverability team makes sure that doesn't happen, which is why it's such a rare occurrence. Our default limits and other vetting of customers, and our willingness to kick customers off the service should they start spamming, ensures we don't show up on those lists.

See Reporting Quickstart section

Bounce Report

Counts: "EmailsHardBounced" or "EmailsSoftBounced" metrics — see Email Delivery metrics

Detail records: email domain "outboundrelayed" logs with "data.action = bounce" and "data.errorType" = "soft" or "hard"; logs must be enabled on per-email domain basis first

See Reporting Quickstart section

Complaint (Spam) Report

Counts: "EmailComplaints" metric — see Email Delivery metrics

Detail records: email domain "outboundrelayed" logs with "data.action = bounce" and "data.errorType" = "soft" or "hard"; logs must be enabled on per-email domain basis first

See Reporting Quickstart section

Unsubscribes

TBD

Coming June 2023

Opens Report

Counts: "EmailOpens" metric

Detail records: email domain "outboundrelayed" logs with "data.action = open"; logs must be enabled on per-email domain basis first, and opens tracking must be enabled for the domain/sender

Now in limited availability, contact us for details

Clicks Report

Counts: "EmailLinkClicks" metric

Detail records: email domain "outboundrelayed" logs with "data.action = click"; logs must be enabled on per-email domain basis first, and click tracking must be enabled for the domain/sender

Now in limited availability, contact us for details

Granularity of Features between Dyn and OCI

Certain features in Dyn have a particular granularity (set per master, per sub-account, per sender, etc.), while OCI may have a different granularity. These are the changes:

Feature Dyn OCI
SMTP Credentials per Sub-account per OCI user
API Credentials per Sub-account per OCI user
Custom Return Path per Master Account (customer) per Email Domain
Postbacks — Bounce per Sub-account for any field in an Email Domain log, so options are numerous
Postbacks — Complaint per Sub-account for any field in an Email Domain log, so options are numerous
Postbacks — Unsubscribe per Sub-account for any field in an Email Domain log, so options are numerous
Open Tracking per Sub-account per Email Domain
Click Tracking per Sub-account per Email Domain
Custom Tracking Domain per Approved Sender per Email Domain
Custom X-Headers per Sub-account per Approved Sender
(planning on per Email Domain with self-serve API)

OCI Email Delivery User Experience Differences

Apart from the differences in overall feature sets, there are some key differences/improvements to note in moving from Dyn Email Delivery to Oracle Cloud (OCI) Email Delivery.

Global vs. Regional

  • Dyn Email is a global service with US-only endpoints; OCI is a regional cloud service with endpoints all over the world
  • Dyn Email has just one SMTP endpoint and one API endpoint to use for most customers, which reach servers in the Phoenix and/or Ashburn regions of the USA
  • OCI Email operates out of a global set of commercial and government regions, each with their own SMTP ("data plane") and API ("control plane") endpoints
  • OCI Email-related resources (email domains, approved senders, suppression lists) are set up/maintained in each region
  • Dyn Email is a standalone email delivery service; Oracle Cloud is a mature cloud offering with wide-ranging set of features

Submitting Email

  • OCI Email requires TLS encryption for all SMTP submissions (Dyn did not)
  • OCI Email Delivery will return an SMTP 254 response code when submitting mail for a recipient that is on the suppression list
    • This differs from Dyn Email Delivery, which accepted the mail and later checked the suppression list, resulting in discarding the email and adding an entry to the "Blocked" report
    • OCI Email's immediate response is seen as a much a better experience, allowing customers to act right away on recipients that are suppressed for whatever reason
  • OCI Email Delivery enforces various service limits, some of which can be extended as long as SPF and DKIM are set up for the email domain(s) in the tenancy. These limits include:
    • maximum emails in a 24-hour period, and a single minute (amounts vary based on type of account); the service will reject emails submitted above these limits, where Dyn would allow sending over a monthly cap and then bill for overage later
    • maximum 2MB raw message size (including headers) — this is same as Dyn Email Delivery, but limit can be increased to 60MB on a per-tenancy basis by request
      • note that billing for messages larger than 2MB are based on 2MB chunks; a 36MB email will be billed for equivalent of 18 emails
  • When using JavaMail to submit email to multiple recipients at once via SMTP, and at least one recipient is on the suppression list, the current SMTP 254 response will cause JavaMail to fail the entire submission even though there may be good recipients included

REST API / SDKs

  • OCI supports only SSL / HTTPS for API requests (Dyn allowed non-SSL HTTP)
  • OCI offers a much richer developer experience, including CLI, REST API, Terraform, and several SDKs for interacting with Email Delivery and other OCI services

Reporting

  • OCI Email characterizes email delivery stages better than Dyn Email:
    • OCI uses the label "accepted" to mean the service has accepted the submission of an email for processing/delivery; this is equivalent to "Sent" in Dyn
    • OCI uses the label "relayed" to mean the service successfully relayed the message to an email provider; this is equivalent to "Delivered" in Dyn. For both OCI and Dyn, this means the message is usually fully delivered to an inbox, but email providers can and do act based on their own internal rules and policies and may do anything from route the message to spam, bounce the message later, or even discard the message silently, among other possibilities.

Reporting and Reporting API Quickstart

OCI Email offers two types of reporting data to show customer email activity of the service:

Type Provided By Setup Needed Available Data
Metrics

Aggregate, time-series count data

  • provided through OCI Monitoring Service
  • metric namespace is oci_emaildelivery
  • filtered by approved sender or email sender domain
  • see web-based metrics in any of these places:
  • for API access, you can ListMetrics to list available metric names, and primarily use SummarizeMetricsData within the Monitoring service API to retrieve metric data for the "oci_emaildelivery" namespace
  • available for 90 days
  • some limitations exist on filtering data depending on the time range specified
Metrics monitoring policies for Email Delivery (inspect and read)

See Available Metrics in OCI Email documentation

  • Emails Accepted
  • Emails Relayed
  • Emails Hard Bounced
  • Emails Soft Bounced
  • Emails Suppressed ("blocked")
  • Email Complaints
  • Total Emails Opened
  • Total Emails Clicked

Available dimensions for metric data:

  • resourceId (approved sender)
  • resourceDomain (email sender domain)

Note: Custom Headers are not available for filtering metric data

Logging Detailed event log data
  • provided through integration with OCI Logging service
  • available by email sender domain
  • can query only up to 14 days at a time

EMAIL_DOMAIN_UPDATE and regular Logging Policies required

Logs must be enabled on a per-domain basis in order to collect and read them

See Email Log Details

Email Metrics Querying

Whether you use web console or API or CLI to access email metrics to retrieve aggregate counts, the key to doing this is the metrics query. You can most easily build queries using the Metrics Explorer in the OCI web console; it allows you to choose one or more filters and automatically derives the appropriate query syntax, which you can then use in other areas (including API and CLI) to run metrics searches.

Metrics queries use Oracle's Monitoring Query Language (MQL) standard to identify the:

  • metric(s) to retrieve
  • interval for breaking up the data returned
  • dimension(s) for limiting data
  • grouping
  • statistic function applied to the data within each interval
  • predicate for limiting results based on a threshold or absence of data

Steps to build a query in Metrics Explorer:

  1. Select compartment (or "root" for the whole tenancy)
  2. Select the metric namespace; for Email Delivery, choose oci_emaildelivery
  3. Select metric name (accepted, relayed, bounced, complaints, open, etc. Note, the Explorer will only show metrics with data available to show)
  4. Select interval (default 1 minute, can be whole-hour or whole-day or a custom number of minutes or hours)
  5. Select statistic (mean is default, can be one of several others including Pxx percentiles)
  6. Select one or more dimensions
    1. dimension name of resourceDomain (email sender domain) or resourceId (approved sender)
    2. based on name choice, dimension value will display domain(s) or sender(s) to choose from
  7. Click Update Chart. The MQL query will display in the left panel and the chart above will display any data it finds
  8. From here you can click Create Alarm based on this query and utilize the Notification or Streaming services to send the data

Here are example queries for some available email-related metrics:

Data Needed Query (time range specified separate from query)
All emails submitted by the service, by minute EmailsAccepted[1m].count()
All emails submitted by the service, by hour EmailsAccepted[1h].count()
Emails relayed (that is, successfully relayed to recipient email provider) by all sales.mydomain.com senders, by day EmailsRelayed[1d]{resourceDomain = "mydomain.com"}.count()
Hard Bounce count by day EmailsHardBounced[1d].count()
Among email submitted by sender marketing@mydomain.com, count of those suppressed ("blocked") per day because recipient is on suppression list EmailsSuppressed[1d]{resourceId = "ocid1.emailsender.oc1.uk-london-1.amaaaaaaooyoulaa6f5dubcaci7stvzf6vdli6wpzw4mtapdeymtgqokkljq"}.count()
Note: resourceId takes the OCID of the approved sender, not thesender email address; you can get the sender OCID from the Approved Senders page.
Emails opened for mydomain.com senders per day EmailsOpened[1d].count()

Querying Email Delivery Metrics via API

For API access, utilize the Monitoring service SummarizeMetricsData API method to retrieve data. Like the Metrics Explorer, the method takes a compartment ID and a SummarizeMetricsDataDetails resource object containing date range, namespace ("oci_emaildelivery" in this case), and MQL query.

Note: MQL seems to require double-quotes when filtering by metric dimensions (resourceDomain or resourceId). When using a language like Python and you have any quotes inside your MQL, it is advised to utilize single quotes around your string values in code, and keep double quotes around strings inside the MQL. See below for examples.

Sample Python metrics list and data queries (replace "compartment_id" with your compartment OCID from the Compartments page):

Data Needed Code Language Phyton
List of available metrics for the "oci_emaildelivery" namespace, sorted by name in ascending order



import oci

config = oci.config.from_file()
monitoring_client = oci.monitoring.MonitoringClient(config)

list_metrics_response = monitoring_client.list_metrics(
compartment_id="ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq",
list_metrics_details=oci.monitoring.models.ListMetricsDetails(
namespace="oci_emaildelivery",
sort_by="NAME",
sort_order="ASC"
)
)
                                                            

Double quotes used above throughout as there is no query involved containing double quotes.

Count of emails accepted per minute within a one-hour window (12:00-13:00 on 2 March 2023) across all senders and sending domains



import oci

config = oci.config.from_file()
monitoring_client = oci.monitoring.MonitoringClient(config)

summarize_metrics_data_response = monitoring_client.summarize_metrics_data(
compartment_id="ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq",
summarize_metrics_data_details=oci.monitoring.models.SummarizeMetricsDataDetails(
namespace="oci_emaildelivery",
query="EmailsAccepted[1m].count()",
start_time=datetime.strptime("2023-03-02T12:00:00.000Z", "%Y-%m-%dT%H:%M:%S.%fZ"),
end_time=datetime.strptime("2023-03-02T13:00:00.000Z", "%Y-%m-%dT%H:%M:%S.%fZ")
)
)
                                                        

Double quotes used above throughout as there is no query involved containing double quotes.

Count of bounces per hour for a 30-hour period for emails sent by all mydomain.com senders



import oci

config = oci.config.from_file()
monitoring_client = oci.monitoring.MonitoringClient(config)

summarize_metrics_data_response = monitoring_client.summarize_metrics_data(
compartment_id='ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq',
summarize_metrics_data_details=oci.monitoring.models.SummarizeMetricsDataDetails(
namespace='oci_emaildelivery',
query='EmailsBounced[1h]{resourceDomain = "mydomain.com"}.count()',
start_time=datetime.strptime('2023-03-04T12:00:00.000Z', '%Y-%m-%dT%H:%M:%S.%fZ'),
end_time=datetime.strptime('2023-03-05T18:00:00.000Z', '%Y-%m-%dT%H:%M:%S.%fZ')
)
)
                                                    

Single quotes used for Python strings so we can still use the required double quotes around the “resourceDomain” value within the MQL query.

Count of opens per day for a 14-day period for all emails sent by a particular approved sender



import oci

config = oci.config.from_file()
monitoring_client = oci.monitoring.MonitoringClient(config)

summarize_metrics_data_response = monitoring_client.summarize_metrics_data(
compartment_id='ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq',
summarize_metrics_data_details=oci.monitoring.models.SummarizeMetricsDataDetails(
namespace='oci_emaildelivery',
query='EmailsOpened[1d]{resourceId = "ocid1.emailsender.oc1.uk-london-1.amaaaaaaooyoulaa6f5dubcaci7stvzf6vdli6wpzw4mtapdeymtgqokkljq"}.count()',
start_time=datetime.strptime('2023-03-01T00:00:00.000Z', '%Y-%m-%dT%H:%M:%S.%fZ'),
end_time=datetime.strptime('2023-03-15T00:00:00.000Z', '%Y-%m-%dT%H:%M:%S.%fZ')
)
)
                                                

Single quotes used for Python strings so we can still use the required double quotes around the “resourceId” value within the MQL query.

Email Log Searching

OCI makes detail log data for Email Delivery activity available through the OCI Logging service. Activity logs are written on a per-email sending domain basis and must be enabled for each domain.

  1. Set up Logging Policies to allow your tenancy to manage, write to, and access logs.
  2. You can use the Deliverability Dashboard page to easily enable logging for all or selected existing email domains. Or to enable for a single one, open up its detail page and click the Logs sidebar menu and enable Outbound Accepted and/or Outbound Relayed logging.
  3. Once enabled for a domain, you can browse and search activity logs most easily via the Log Search page in the OCI web console

Email Delivery Log Types

By default, OCI logs activity for most every service whether it comes from web console, API, CLI, or other interfaces. Assuming you have activity, if you do a basic search with no filter you'll see examples such as listing of resources and changes to resources. Email Delivery offers two types of logs to track email domain activity:

  • OutboundAccepted — successful or failed inbound email submission activity
    • successful submission (emails accepted by Email Delivery service)
    • accepted but suppressed from delivery because recipient is on the suppression list
    • invalid recipient or sender (including sender not on Approved Senders list)
  • OutboundRelayed — activity related to outbound email delivery and engagement
    • successful deliveries
    • bounces and spam complaints
    • opens, clicks

You can enable one or both of these logs for each email domain.

Common Email Domain Log Events and Filtering

This is a summary of the information detailed on the Logging Details for Email Delivery page. See this page for example log entries.

Event Log Type Log Filter(s) — assume "AND" for multiple Notes
Email accepted for delivery by OCI Email outboundaccepted data.action = 'accept'  
Email rejected/blocked because recipient is on suppression list outboundaccepted data.action = 'accept' data.errorType = 'Recipient suppressed' data.smtpStatus would have formal reason
Email successfully relayed to recipient email provider outboundrelayed data.action = 'relay'  
Emails relayed for a particular custom header value outboundrelayed data.action = 'relay' and data.headers."x-campaignid" = '999' If custom header field name has any non-alphanumeric characters (such as a dash), the name itself must be surrounded with double quotes
Email bounced because of outbound delivery issue (recipient unknown, domain unknown, spam-related refusal, etc.) outboundrelayed data.action = 'bounce'

Can further filter on "data.errorType" for type of bounce:

  • hard
  • soft

Other fields included which have additional bounce info, such as:

  • data.bounceCategory
  • data.bounceCode
  • data.smtpStatus
  • data.message
Recipient registered spam complaint outboundrelayed data.action = 'complaint'  
Recipient opened an email outboundrelayed data.action = 'open'

Extra field may have information about recipient's email client and operating system:

  • data.userAgent
Opens for a given custom header value outboundrelayed data.action = 'open' and data.headers.region = 'Northeast'  
Recipient clicked a link in an email outboundrelayed data.action = 'click' TBD

Searching Email Delivery Logs

Similar to OCI metrics (where Email Delivery activity count data is stored), detailed activity log searching relies on queries fashioned from the Logging Query Language to identify scope and filter search results. A very simple, broad example:



search "ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq"
| type='com.oraclecloud.emaildelivery.emaildomain.outbound*'
| sort by datetime desc
                

The above example uses a wider scope of an entire compartment (referred to by its OCID) and returns only email submission/inbound and outbound activity log entries for email sending domains that have logging enabled. Results are sorted by date in descending order. Some other more specific examples:

Data Needed Code
Count of emails accepted for a given custom header value


search "ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq/Default_Group/mydomain_com_outboundaccepted"
| data.action = 'accept' and data.headers.region = 'Europe'
| count
                                            
Failed Submissions for sender domain mycompany.com (no matter the reason)


search "ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq/Default_Group/mydomain_com_outboundaccepted" 
| data.errorType!='' and source='mycompany.com'
| sort by datetime desc
                                        

("errorType" is not present for successful submissions. We're searching only the "outboundaccepted" log because all failed submissions go there. If you need to search across multiple domains, specify only the compartment OCID and use same filtering clause.)

Suppressed submissions across all sender domains


search "ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq" 
| data.errorType='Recipient suppressed'
| sort by datetime desc
                                    

Particularly helpful to know which of your email submissions are being blocked/suppressed because the recipient is on the suppression list

Successful relayed email (that is, accepted by recipient email provider for delivery) for mycompany.com


search "ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq" 
| data.action='relay'
| sort by datetime desc
                                

for just sending domain mycompany.com:



search "ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq/Default_Group/mycompany_com_outboundrelayed" 
| data.action='relay' and source='mycompany.com'
| sort by datetime desc
                            

You can use just the "source" field to look for sending domain and not specify the log group + log name in the "search" clause; but searching the specific log (like we do here) should be more performant if you have multiple sending domains.

All hard bounces across all sending domains


search "ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq"
| data.action='bounce' and data.errorType='hard'
| sort by datetime desc
                        
Count of opens for a given custom header value


search "ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq"
| data.action='open' and data.headers.region='Northeast'
| count
                    

Searching Email Delivery Logs via API

OCI offers SDKs in several major languages and includes the OCI Logging API with its SearchLogs method. This method takes a date/time range and an object with the same logging query syntax used in the web console, and a few other optional fields.

"List" APIs like SearchLogs return a default maximum number of records at a time. If the response has the default max number of records, it should include an "opc-next-page" field. To get subsequent pages of records, invoke the same request with the previous response's "opc-next-page" value specified in the "page" parameter. You may be able to increase the per-page max record count using the "limit" parameter. See OCI API List Pagination for more information and important details.

See the Using the OCI Logging API page for examples in several languages. Some more real-world Email Delivery examples in Python:

Data Needed Code Language Phyton
Bounces during a three-day period, sent from mycompany.com senders



import oci
from datetime import datetime
config = oci.config.from_file()
loggingsearch_client = oci.loggingsearch.LogSearchClient(config)

search_logs_response = loggingsearch_client.search_logs(search_logs_details=oci.loggingsearch.models.SearchLogsDetails(time_start=datetime.strptime("2023-03-01T00:00:00.000Z", "%Y-%m-%dT%H:%M:%S.%fZ"), time_end=datetime.strptime("2023-03-04T00:00:00.000Z", "%Y-%m-%dT%H:%M:%S.%fZ"), search_query="search \"ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq/Default_Group/mycompany_com_outboundrelayed\" | where data.action='bounce'", is_return_field_info=False))
print(search_logs_response.data)
                                
Emails successfully relayed for all sending domains in a compartment over a one-hour period



import oci
from datetime import datetime
config = oci.config.from_file()
loggingsearch_client = oci.loggingsearch.LogSearchClient(config)

search_logs_response = loggingsearch_client.search_logs(search_logs_details=oci.loggingsearch.models.SearchLogsDetails(time_start=datetime.strptime("2023-03-03T08:55:00.000Z", "%Y-%m-%dT%H:%M:%S.%fZ"), time_end=datetime.strptime("2023-03-03T09:55:00.000Z", "%Y-%m-%dT%H:%M:%S.%fZ"), search_query="search \"ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq\" | where data.action='relay'", is_return_field_info=False))

print(search_logs_response.data)
                            
All Gmail and Comcast recipients successfully relayed to during a 24-hour period



import oci
from datetime import datetime
config = oci.config.from_file()
loggingsearch_client = oci.loggingsearch.LogSearchClient(config)

search_logs_response = loggingsearch_client.search_logs(search_logs_details=oci.loggingsearch.models.SearchLogsDetails(time_start=datetime.strptime("2023-03-03T08:55:00.000Z", "%Y-%m-%dT%H:%M:%S.%fZ"), time_end=datetime.strptime("2023-03-03T09:55:00.000Z", "%Y-%m-%dT%H:%M:%S.%fZ"), search_query="search \"ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq\" | where data.action='relay' and (data.receivingDomain='gmail.com' or data.receivingDomain='comcast.net') | sort by datetime DESC", is_return_field_info=False))

print(search_logs_response.data)
                        
Count of emails delivered by recipient domain for a given custom header value



import oci
from datetime import datetime
config = oci.config.from_file()
loggingsearch_client = oci.loggingsearch.LogSearchClient(config)

search_logs_response = loggingsearch_client.search_logs(search_logs_details=oci.loggingsearch.models.SearchLogsDetails(time_start=datetime.strptime("2023-05-12T17:30:00.000Z", "%Y-%m-%dT%H:%M:%S.%fZ"), time_end=datetime.strptime("2023-05-12T18:00:00.000Z", "%Y-%m-%dT%H:%M:%S.%fZ"), search_query="search \"ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq\" | data.action = 'relay' and data.headers.\"x-campaignid\" = '999' | summarize count(data.receivingDomain) as Count by data.receivingDomain as RecipientDomain", is_return_field_info=False))

print(search_logs_response.data)
                    

Result:




{
    "fields": null,
    "results": [
        {
            "data": {
            "Count": 2,
            "RecipientDomain": "gmail.com"
           }
        },
        {
            "data": {
            "Count": 1,
            "RecipientDomain": "yahoo.com"
           }
        }
    ],
      "summary": {
        "field_count": null,
        "result_count": 2
    }
}
        

Dyn Email → OCI Email API Method Mapping

Category Dyn Email API OCI Email API Equivalent Notes
Endpoints api.email.dynect.net (or variation) Each Oracle Cloud region has its own API endpoint, such as:
  • Ashburn (Virginia, USA): https://ctrl.email.us-ashburn-1.oci.oraclecloud.com
  • London: https://ctrl.email.uk-london-1.oci.oraclecloud.com
  • Singapore: https://ctrl.email.ap-singapore-1.oci.oraclecloud.com
 
Authentication API Key, one per account, provided:
  • V2 through "apikey=..." querystring variable
  • V4 through "Authorization" HTTP header set to "apikey=..."; also requires "Accept" header
API Key, an RSA key pair generated through OCI user account
Key is used with OCI CLI configuration and loaded through one of many SDKs
OCI API Keys work across regions; they are not region-specific; can have multiple OCI API keys for different uses
OCI requires HTTPS (SSL) access for all API requests
Accounts /accounts (GET or POST)
/accounts/delete (POST)
See the Identity API (use Table of Contents in left sidebar to browse the methods) for info on managing resources like users, groups, compartments, and policies.
Read the Overview of Identity and Access Management for a good introduction to Oracle Cloud accounts and access.
 
X-Headers /accounts/xheaders (GET or POST) Currently managed only via support ticket to Email Delivery  
Email Sending Domains - Email Domain API (see Table of Contents for links to methods)  
Approved Senders /senders (GET or POST)
/senders/details (GET)
/senders/status (GET)
/senders/delete (POST)
Approved Sender API (see Table of Contents for links to methods)
(strongly recommended to set up Email Domains as well, to facilitate DKIM and other key features)
 
DKIM /senders/dkim (POST) DKIM API (see Table of Contents for links to methods)
Requires email domain to be set up first; DKIM is associated with email domain in OCI
 
Suppressions /suppressions (GET or POST)
/suppressions/count (GET)
/suppressions/activate (POST)

/recipients/status (GET)
/recipients/activate (POST)

("recipients" is same as "suppression" in this case; they act on the same data)
Suppression API (see Table of Contents for links to methods)  
Custom Tracking Domains (V4 API only) /custom-domain (POST)
/custom-domains (GET)
/custom-domain/[id] (GET)
/custom-domain/[id]/validity (GET)
/custom-domain/[id] (DELETE)

/senders (GET)
/sender/[id]/custom-domain (GET, POST, PATCH, DELETE)
Under development, contact us for timeline details  
Reporting — Sent

V2

  • /reports/sent
  • /reports/sent/count

V4

  • /emails (with delivered=0, or leave out delivered param)
  • /email/counts (with delivered=0, or leave out delivered param)

For detail records, use OutboundAccepted log with query filter like this to show only successfully submitted emails:

data.action = 'accept' and data.errorType = ''

For counts, use EmailsAccepted metric, available by approved sender or email domain only; custom header filtering not supported, use log with aggregation function to get counts

"Sent" in Dyn is more about acceptance of the email for processing/delivery
Oracle Cloud Developer Resources
Reporting — Delivered

V2

  • /reports/delivered
  • /reports/delivered/count

V4

  • /emails (with delivered=1)
  • /email/counts (with delivered=1)

For detail records, use OutboundRelayed log with query filter like this to show only relayed (delivered to recipient email provider) emails:

data.action = 'relay'

For counts, use EmailsRelayed metric, available by approved sender or email domain only; custom header filtering not supported, use log with aggregation function to get counts

Oracle Cloud Developer Resources
Reporting — Issues (V2 only) /reports/issues
/reports/issues/count

For detail records, use OutboundAccepted log with query filter like this to show failed email submissions:

data.action = 'accept' and data.errorType != ''

Not available as a metric, use log query with aggregation function to get counts

Oracle Cloud Developer Resources
Reporting — Bounces

V2

  • /reports/bounces (detail)
  • /reports/bounces/count (counts)

V4

  • /bounces (detail)
  • /bounce/counts (counts)
  • /bounce/categories
  • /bounce/[user_id]/[sender_id]/[bounce_time]/[email_id]

For counts, use EmailsHardBounced and EmailsSoftBounced metrics, available by approved sender or email domain only; custom header filtering not supported, use log with aggregation function to get counts

No equivalent to "/bounce/categories" in OCI; values are returned through "data.bounceCategory" field in Log detail. Current list of possible values:

  • spam-related
  • content-related
  • virus-related
  • quota-issues
  • invalid-sender
  • routing-errors
  • protocol-errors
  • bad-connection
  • no-answer-from-host
  • message-expired
  • inactive-mailbox
  • bad-mailbox
  • bad-domain
  • relaying-issues
  • policy-related
  • bad-configuration
  • other
  • unclassified
Oracle Cloud Developer Resources
Reporting — "Blocked" (suppressed)

V2

  • /reports/blockedemail
  • /reports/blockedemail/count

V4

  • /blocked-emails
  • /blocked-email/counts
  • /blocked-email/reasons

For detail records, use OutboundAccepted log with query filter like this to show only emailed suppressed because recipient is on suppression list:

data.action = 'accept' and data.errorType = 'Recipient suppressed'

For counts, use EmailsSuppressed metric, available by approved sender or email domain only; custom header filtering not supported, use log with aggregation function to get counts

No equivalent in OCI for /blocked-email/reasons; Email Domain Log data will have only "Recipient suppressed" in the data.errorType field, no more specific reason

Oracle Cloud Developer Resources
Reporting — Complaints (V2 only) /reports/complaints
/reports/complaints/count

For detail records, use OutboundRelayed log with query filter like this to show only spam complaints:

data.action = 'complaint'

For counts, use EmailComplaints metric, available by approved sender or email domain only; custom header filtering not supported, use log with aggregation function to get counts

Oracle Cloud Developer Resources
Reporting — Opens (V2 only) /reports/opens
/reports/opens/unique

/reports/opens/count
/reports/opens/count/unique

For detail records, use OutboundRelayed log with query filter like this to show only opens:

data.action = 'open'

For counts, use TotalEmailsOpened and UniqueEmailsOpened metric, available by approved sender or email domain only; custom header filtering not supported, use log with aggregation function to get counts

Oracle Cloud Developer Resources
Reporting — Clicks (V2 only) /reports/clicks
/reports/clicks/unique

/reports/clicks/count
/reports/clicks/count/unique

For detail records, use OutboundRelayed log with query filter like this to show only clicks:

data.action = 'click'

For counts, use TotalEmailsClicked and UniqueEmailsClicked metrics, available by approved sender or email domain only; custom header filtering not supported, use log with aggregation function to get counts

Oracle Cloud Developer Resources
Send /send (POST, V2 only) HTTPS Email submission in limited availability, contact us for details  

Postback Integration

Oracle Cloud offers multiple ways to set up automatic notification of email submission- and delivery-related events, similar to the postback feature in Dyn. In addition to the bounce, spam complaint, and unsubscribe events available in Dyn Email Delivery, Oracle Cloud allows for notification based on numerous events and filtering, such as:

  • email hard bounced (recipient email not known, spelled wrong, etc.)
  • email soft bounced (recipient mailbox full, could indicate content-based spam filtering from recipient domain, etc.)
  • recipient registered spam complaint
  • email suppressed (email not attempted for delivery because recipient is on suppression list)
  • recipient opened email
  • recipient clicked a link in an email
  • ... any of above with additional filtering such as based on a custom header value

Options

For postback-like functionality, we recommend the use of the Service Connector Hub and Logging services within Oracle Cloud to provide the ability to set up automatic log event monitoring. Service Connectors integrate certain Oracle Cloud services together to facilitate data transfer.

In the case of Email Delivery, email domain log data (provided by the Logging service) provide the data for events such as bounces, complaints, unsubscribes, opens, and clicks. The data would then be sent to one of several different services such as the Notification or Functions services, to provide means for sending that data to your own systems.

Oracle Cloud offers various other options to transmit event data, such as with the Streaming service to external systems like Splunk. Contact us to discuss opportunities.

Postbacks via Notification

This solution would likely require some refactoring for any existing Dyn HTTP handler code you have, but the setup on the Oracle Cloud side is relatively simpler and less involved compared to the Function Code solution below — so it is worth exploring. The solution involves integrating the Notification service to send event data to one of several targets:

  • HTTPS custom URL (querystring not supported)
  • Email
  • Slack
  • PagerDuty
  • SMS

This solution sends event data in JSON blob form; so your code behind that URL would need to parse out the JSON to pull out the appropriate fields, similar to any existing HTTP handler code you have. Follow the basic steps below to set up an example notification "postback".

Write/Update HTTP Handler Code to Parse Email Domain Log Events

Email Domain event log data is structured differently than in Dyn Email so any existing code will need to be refactored to match. Most email domain log data will look like this (though some fields will differ depending on the event type):



{
    "data": {
    "action": "bounce",
    "bounceCategory": "bad-mailbox",
    "bounceCode": "5.1.1",
    "errorType": "hard",
    "message": "Suppressed recipient invalid-recipient@gmail.com for email from sender@mydomain.com: bad-mailbox hard bounce",
    "messageId": "20230404133157.059822@source-server",
    "originalMessageAcceptedTime": "2023-04-04T17:31:58.807Z",
    "receivingDomain": "domain.com",
    "recipient": "invalid-recipient@gmail.com",
    "recipientMailServer": "gmail-smtp-in.l.google.com (TCP|130.35.116.120|41521|173.194.76.27|25) (mx.google.com ESMTP b16-20020ad8139351002e63fddfad8909160wrn.440 - gsmtp)",
    "reportGeneratedTime": "2023-04-04T17:31:58Z",
    "sender": "sender@mydomain.com",
    "senderCompartmentId": "ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq",
    "senderId": "ocid1.emailsender.oc1.uk-london-1.amaaaaaaooyoulaawiybkhjpvsfkm3jsey3bmmzfwoca3yjk7uiq2s3xt4ga",
    "smtpStatus": "550-5.1.1 The email account that you tried to reach does not exist. Please try the recipient's email address for typos or spaces. Learn more at https://support.google.com/mail/?p=NoSuchUser b16-20020adfe650000000"
    },
    "id": "bc55d3d4-b33c-4c3c-98f8-d3c0b9af5bf3",
    "oracle": {
    "compartmentid": "ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq",
    "ingestedtime": "2023-04-04T17:32:06.826Z",
    "loggroupid": "ocid1.loggroup.oc1.uk-london-1.amaaaaaaooyoulaa7f2hx3ebgdkfnhbhnemsmgq7cpoilzxjcd6yh5df46ta",
    "logid": "ocid1.log.oc1.uk-london-1.amaaaaaaooyoulaa5xmtlvnez5ekyfdmr5sbw5qywb5jxggyjcmucfst2iva",
    "tenantid": "ocid1.tenancy.oc1..aaaaaaaapgqj5fjeku52qogywp4agshdscgog4gaeuk5uxfccyx5fofpg2oa"
    },
    "source": "mydomain.com",
    "specversion": "1.0",
    "time": "2023-04-04T17:32:00.221Z",
    "type": "com.oraclecloud.emaildelivery.emaildomain.outboundrelayed"
    }
        

Fields above such as "bounceCategory" and "bounceCode" will not be present for non-bounce events. Other field values will differ for other event types, such as "action", "errorType", "smtpStatus", and "message". See the Email Log Searching section of this guide for more information.

Note that Service Connector Hub will batch multiple log entries into a single request if they occur close enough to each other, so your handler should be able to handle multiple log entries within a single JSON request body.

Enable Logging for Email (Sending) Domains

Follow these steps to enable logging for one or more of your sending email domains. Once this is done, you will start to see helpful event logging to gain insight into your sending activity, in addition to automatic metrics.

Create Notification Topic + Subscription

To utilize the Notification Service as a target for email domain event log data, you must first set up a topic and subscription. Dyn customers who wish to keep their postback solution would use the HTTPS URL type of subscription, but you can use other types instead.

For HTTPS URL subscriptions you must first host your HTTP handler on a secure website because the setup process will send an HTTP POST to the handler with a confirmation link in the request body. This URL must be opened to confirm the handler is reachable and enable it for active use.

Use Email Domain Log Search to Create Service Connector

While you can create a Service Connector through the Service Connector Hub web console UI, it is easier to first define your email domain log search criteria, then create a service connector from there.

  1. First, open the log search page to define an email domain log search for the data you want to have sent / "posted back"
    1. Be sure to clear any compartment-wide or tenancy-wide choices for logs to search, and do not choose "_Audit" for log group
    2. You need only select the specific email domain log (outboundrelayed takes care of most event types) to ensure your notification handler receives just the appropriate events
  2. Once your search criteria is set, click the Create service connector button. The web console copies the relevant log search data into a new service connector form.
    1. Update the Connector name with a more descriptive name
  3. Under Configure service connector, the Source should already be set to "Logging" and the Configure source panel should have your log search criteria filled in
  4. Still under Configure service connector, select "Notifications" as a Target. A Configure target panel should appear further down the page.
  5. Under Configure target, select the topic you created earlier
    1. Leave the "Send raw messages" option selected
  6. Click Create to save the new service connector.

At this point, any log events that occur which match your log search criteria, will be sent to your HTTPS URL via HTTP POST, and the body will contain one or more log events.

Postbacks via Function Code

Inspired by Nitin Soni's solution. Other function samples available via the Oracle Functions Samples repo on GitHub.

This solution provides postback-like functionality without need to modify existing customer HTTP handler code, by utilizing Oracle Cloud's Service Connector Hub to integrate email domain log events with the Functions service. Function code would be written and deployed within Oracle Cloud to make HTTPS requests to your existing or new web postback handler. For a much simpler but less flexible alternative, see the Postbacks via Notification section.

Create VCN, Subnet, and Egress Internet Connectivity

  1. Once you have a customer tenancy created, login to the OCI web console
  2. Go to Virtual Cloud Networks
  3. Click Create VCN. The create panel popup appears.
  4. Enter the following:
    1. Name (such as "virtual-network")
    2. Compartment if applicable
    3. Under IPv4 CIDR Blocks — enter a CIDR range to contain a private subnets, such as 10.0.0.0/16
    4. Click Create VCN at the bottom. The new VCN detail page appears.
  5. Under Subnets, click Create Subnet. The create panel appears.
  6. Create a private subnet with the following:
    1. Name (such as "private-subnet")
    2. Compartment if applicable
    3. Under IPv4 CIDR Block, enter a subnet range of the CIDR block above, such as 10.0.1.0/24
    4. Under Route Table select "Default Route Table"
    5. Under Subnet Access click Private Subnet
    6. Under DHCP Options select "Default DHCP Options"
    7. Under Security Lists select "Default Security List"
    8. Click Create Subnet at the bottom. The new subnet appears in the subnet list on the VCN detail page with "Provisioning" state, then should switch to "Available" in short order.
  7. In the left sidebar, click NAT Gateways. The create panel appears.
    1. Enter a Name, such as "nat-gateway"
    2. Leave the default "Ephemeral Public IP Address" selected
    3. Click Create NAT Gateway at the bottom. The gateway appears in the NAT Gateways list.
  8. In the left sidebar, click Route Rules. The Route Tables list appears.
  9. Click the "Default Route Table" entry
  10. Click Add Route Rules. The create panel appears.
    1. Under Target Type, choose NAT Gateway
    2. For Destination CIDR Block, enter "0.0.0.0/0" to allow notifications to go anywhere on the Internet; or you can enter a more specific range that contains your event handler/service. If you choose to use a more restrictive range, for security purposes we recommend going into the Security Lists to restrict any egress rules accordingly.
    3. Under Target NAT Gateway, select the gateway you created above
    4. Click Add Route Rules. The routing rules list is updated to include the rule you just created.
  11. At this point your tenancy has the resources needed to connect out to the Internet to send out event notifications to your event handler/service.

Enable Logging for Email (Sending) Domains

Follow these steps to enable logging for one or more of your sending email domains. Once this is done, you will start to see helpful event logging to gain insight into your sending activity, in addition to automatic metrics.

Set up Function(s) for Desired Event(s)

Functions are used as a sort of "callback" to perform a notification or other business action as a result. Combined with Service Connectors (below), you can set up automatic notification for one or more events such as a bounce, spam complaint, open, click, etc.

A more detailed quickstart is available, and you can import template functions as well, but here are the basic steps to set up a function:

  1. Navigate to Policies in the OCI web console.
  2. Click Create Policy. The create policy panel appears.
    1. Enter a Name for the policy, such as "functions-policy"
    2. Enter a helpful Description
    3. Under Compartment, choose the tenancy name (Functions policies must be applied across the tenancy at the "root" level)
    4. Under Policy use cases, select "Functions". The console will then fill in the Policy Statements with the default recommended set of policies.
    5. Click either Group or Dynamic Group, and select the appropriate option to grant permissions to them.
      1. By default you should have an "Administrators" group which is fine; or you can back out and create a more limited group to use.
      2. For Location, make sure the tenancy name ("root") compartment is chosen
    6. Click create.
  3. Navigate to the Functions service in the OCI web console.
  4. Click Create application.
    1. Enter a Name such as "email-event-notifications-app"
    2. Confirm the VCN you created earlier is selected under VCN
    3. Under subnets, choose the private subnet you created earlier
    4. Click Create. The application detail page now shows.
    5. In the left sidebar, click Logs
    6. In the "Logs" table, click the switch under "Enable Log" to enable function invocation logging for this new application. This will help with troubleshooting.
  5. Click Functions in the left sidebar
  6. Click Create function, then click Create in Code Editor. The code editor window appears and launches the editor (allow for about a minute while the system creates the equivalent of a virtual machine to support the shell). You should see the application you just created in the left sidebar.
  7. When finished loading, you should see a "Select a creation method" dropdown box in the header bar; if not, right-click your application name in the left sidebar and choose Create function. You can import working function examples from a few places:
    1. Create from a template — choose from one of several different languages to create a very basic "Hello World"-type example
    2. Create from a sample — Oracle offers a growing library of functions of many types, in various languages; these are also available in the Oracle Functions Samples repository on GitHub
    3. Create from a code repository — choose from a library of functions hosted in a repository
  8. For the purpose of this quickstart, click Create from a template and click Python in the language list
    1. Enter a name for your function, such as "bounce-notification", and hit Enter. If you see a popup about deploying and pushing to a remote branch, click OK to that.
    2. The new function should appear under your new application in the left sidebar. Click on the main code file, "func.py" to open it in the editor.
    3. Each function, regardless of language, will have a handler function which takes a context ("ctx") object and JSON "data" object as input. The Python sample reads the data into a JSON body object and gets the "name" field value from it, then returns a "Hello " response back. It also logs an "INFO" message which will show up in the function invocation log for the application, if you enabled that earlier).
    4. Replace the contents of func.py with the following, which is a sample email bounce event notification:

      
      
      """                                             
      OCI Function for Email Delivery Event Notification (Python example)
      
      This function takes one or more OCI Email Domain log events and sends HTTP "postback"
      notifications to a website address, similar to the Dyn Postback feature.
      
      This function can be modified to support any type of log item.
      
      Requirements/Dependencies:
      
      OCI Email Delivery service configuration (https://docs.oracle.com/en-us/iaas/Content/Email/Reference/gettingstarted.htm)
      - user with SMTP credentials
      - required policies
      - email domain with SPF + DKIM set up
      - email domain logging enabled
      - approved sender created
      
      OCI Functions configuration (https://docs.oracle.com/en-us/iaas/Content/Functions/Tasks/functionsquickstartcloudshell.htm)
      - VCN + subnet (private, using NAT Gateway)
      - required policies
      - application (with optional logging enabled)
      - function with code to read Log and send HTTP request
      
      OCI Service Connector Hub integration with Logging (https://docs.oracle.com/en-us/iaas/Content/service-connector-hub/create-service-connector-logging-source.htm)
      - service connector using:
      -- VCN subnet
      -- Logging as source, pointed at Email Domain "outboundrelayed" log, and log rule task of "data.action = bounce" (can fine-tune this to be more or less granular)
      -- OCI Function as target
      """
      
      import io
      import json
      import logging
      import requests
      
      from fdk import response
      
      bounce_postback_url_with_params = "https://mydomain.com/dyn-http-handler.php?type=b&e={email}&bt={bouncetype}&bc={bouncecode}&br={bouncerule}&dc={diagnostic}&s={status}&message={message}"
      bounce_postback_url_no_params = "https://mydomain.com/dyn-http-handler.php"
      
      def handler(ctx, data: io.BytesIO=None):
      
          """
          Main Function handler routine, takes 
          """
      
          # Input is one or more Log events; parse into JSON and loop through them
          try:
          logs = json.loads(data.getvalue())
      
          for item in logs: 
      
          # Pull postback field values from log event
          log_data = item['logContent']['data']
      
          # For custom headers, gather them (if present) and loop through to add to request parameters
          #headers = log_data['headers']  # gives a dictionary of (header_name -> value) pairs
      
          # Other possibly helpful fields to include, which were not available in Dyn:
          # (see https://docs.public.oneportal.content.oci.oraclecloud.com/en-us/iaas/Content/Logging/Reference/details_for_emaildelivery.htm for list of all available fields)
          #event_time = item['logContent']['time']  # ISO 8601 format; for epoch time, use item['datetime']
          message = log_data['message']  # this is high-level, human-readable summary of bounce reason
          #message_id = log_data['messageId']  # original SMTP message ID
          #action = log_data['action']  # type of log event (accept, relay, bounce, open, click, etc.)
          #sender = log_data['sender']  # sender of the message
          #recip_domain = log_data['receivingDomain']  # Recipient domain (gmail.com, comcast.net, hotmail.com, etc.)
      
          params_values = {
          "email": log_data['recipient'],
          "bouncetype": log_data['errorType'],
          "bouncecode": log_data['bounceCode'],
          "bouncerule": log_data['bounceCategory'],
          "diagnostic": log_data['smtpStatus'],
          "status": log_data['bounceCode'],
          "message": message
          }
      
          # Traditional URL querystring GET method, no request body
          http_response = requests.get(bounce_postback_url_with_params.format(**params_values))
          # GET with parameters passed in request body
          #http_response = requests.get(bounce_postback_url_no_params, params=params_values)
          # POST with parameters passed in request body
          #http_response = requests.post(bounce_postback_url_no_params, data=params_values)
          logging.getLogger().info("Postback sent: {} Response: HTTP {} --- {}".format(http_response.url, http_response.status_code, http_response.text.replace('\n', '')))
      
      except (Exception, ValueError) as ex:
          logging.getLogger().error(str(ex))
          return
                              

      Editor will automatically save code updates by default, but click File menu, then Save to be sure.

    5. Double-click requirements.txt in the left pane to open that file, then add "requests" to the list of Python packages ensure it will be imported:

      
      
      fdk
      requests
      
                          

      Click File → Save.

  9. With the function code in place, deploy the function using the Cloud Shell CLI:
    1. If the terminal window isn't yet open at the bottom of the editor, click the Terminal menu and click New Terminal
    2. Set up your Cloud Shell environment with the Functions ("Fn") CLI:
                                                     
      fn list context
      fn use context [long_region_name]    (replace region long name as appropriate)
      fn update context oracle.compartment-id ocid1.[compartment_ocid]    (OCID for root compartment is the tenancy OCID)
      fn update context registry [short_region_code].ocir.io/[tenancy-namespace]/[repo-name-prefix]     (replace "lhr" with region short name, such as phx, iad, fra, syd)                                    
                          
    3. If you've not yet created an OCI auth token for your OCI username, do so now.
    4. Back in the terminal window, login to the Docker Registry specified in the Fn Project CLI context above; specify your OCI auth token as password:
      docker login -u '[tenancy-namespace]/[oci-username]' lhr.ocir.io (or phx, iad, fra, syd, etc.)
      "tenancy-namespace" is available under the "Getting Started" steps on the Application detail page, or on the tenancy detail page under "Object storage settings". Replace "lhr" with the short code for the region you're in (such as "iad" for Ashburn, "phx" for Phoenix, etc.).
    5. Switch to the folder containing your new function, similar to this (get the application OCID from the application details page):
      cd ~/oci-ide-plugins/faas-artifacts/[application_ocid]/bounce-notification/
    6. Create a new code repository to contain your new function. This is required for deployment.
    7. While in the folder containing your new function, run these commands to commit the function code to a remote git repository; this is required for deployment:
      
      
      git init
      git add .
      git commit -m 'Initial commit of function code' 
      git remote add origin <(URL of code repository created above)>
      git branch -M main
      git push -u origin main        
                      
      Enter username and password for code repository if/when prompted.
    8. Finally, deploy your application:
      fn -v deploy --app email-event-notifications-app
      A Python virtual environment and the function app code are packaged up into a Docker container and deployed into the OCI Container registry, which OCI Functions will use to run your function code.
    9. You can test the function by simulating sending of log event data to it. In the code editor Terminal window:
      
      
      echo -n '[{"datetime":1678283756324,"logContent":{"data":{"action":"accept","envelopeSender":"erik+test@mydomain.com","errorType":"Recipient suppressed","message":"Suppressed email from erik+test@mydomain.com → nowaythisisreal6543210@mydomain.com","principalId":"ocid1.user.oc1..aaaaaaaajf6yi5qzyb35af3fxj454n4kyjfyurdvdcnodkjkvg2dabr2uzdq","receivingDomain":"mydomain.com","recipient":"nowaythisisreal6543210@mydomain.com","senderCompartmentId":"ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq","senderId":"ocid1.emailsender.oc1.uk-london-1.amaaaaaaooyoulaa6f5dubcaci7stvzf6vdli6wpzw4mtapdeymtgqokkljq","smtpStatus":"254 4.7.1 -  nowaythisisreal6543210@mydomain.com is suppressed for sender ocid1.emailsender.oc1.uk-london-1.amaaaaaaooyoulaa6f5dubcaci7stvzf6vdli6wpzw4mtapdeymtgqokkljq"},"id":"cedfaf44-89a9-4a6e-8057-233a0176b0a3","oracle":{"compartmentid":"ocid1.compartment.oc1..aaaaaaaawqhwudf3pag5gohpzslu7tn4rl2d32bufkgmfzzr4wq7aznylpeq","ingestedtime":"2023-03-08T13:56:03.976Z","loggroupid":"ocid1.loggroup.oc1.uk-london-1.amaaaaaaooyoulaa7f2hx3ebgdkfnhbhnemsmgq7cpoilzxjcd6yh5df46ta","logid":"ocid1.log.oc1.uk-london-1.amaaaaaaooyoulaao3cik345bpfz44miozkxiqi4chucpnjawl4lvxxezenq","tenantid":"ocid1.tenancy.oc1..aaaaaaaapgqj5fjeku52qogywp4agshdscgog4gaeuk5uxfccyx5fofpg2oa"},"source":"mydomain.com","specversion":"1.0","time":"2023-03-08T13:55:56.324Z","type":"com.oraclecloud.emaildelivery.emaildomain.outboundaccepted"}}]' | fn invoke email-event-notifications-app bounce-notification           
                  
      Note, if you use a different log event, be sure the contents do not contain a single quote or the test will fail with a parsing error. You should be able to escape them for purpose of the test. In real use these log entries can have single quotes; it's just that the CLI testing with "echo" requires the single quotes around the whole string.
    10. From here, you can troubleshoot your function simply by (1) editing the function code, (2) saving, and (3) re-running the deploy command above (no Git updating). Your code, once re-deployed, is ready for use to try with the CLI test above.
    11. If you enabled logs for your application above, you can go to the Logging service to see a history of invocations, and any messages that are output from the code. The "logging.getLogger().info" (or error or other) lines will each output a log event, so these can be useful in troubleshooting.
    12. Once your function is working, proceed to connect your email domain logs with it, using Service Connector.

Create Service Connector to integrate email domain log events and Function(s)

The Service Connector Hub service in Oracle Cloud offers the ability to integrate services within the cloud and pass information between them. In this case, we're connecting the Logging service (which stores activity for Email Delivery sending domains) with the Functions service (which will accomplish the HTTP postback call). Log data is passed between the services, providing a great deal of flexibility and power in creating a custom solution for your needs.

  1. In the OCI web console, navigate to the Service Connectors page
  2. Click Create service connector. A create panel appears.
  3. Enter a connector name (alphanumeric and special characters, no spaces) and helpful description
  4. Under Configure service connector:
    1. for Source choose Logging
    2. for Target choose Functions
  5. Under the Configure source panel:
    1. Select the compartment name, Log group, and log for the email domain you wish to monitor for event notifications. Log names will usually contain the email domain and either "outboundaccepted" or "outboundrelayed". See Log Details for Email Delivery documentation for more info, or consult the table for common email domain log events and how to filter on them.
    2. For Log filter task, choose one or more filters to limit the scope of the notifications for this function. For instance, these two filters will catch all emails blocked from delivery because the recipient is on the suppression list:

      data.action = accept data.errorType = Recipient suppressed

      As you add filters, you can see the query syntax update on the right, and can click "View logs" to see any matches for that filter in the last six hours. Note that a service connector can only

  6. Under the Configure target panel:
    1. Under Configure target connection, choose the Compartment containing the function. If you see a warning panel pop up below, suggesting you add a policy for service connector to run functions, click Create on the right side of the panel to do this as it will be needed to authorize the integration.
    2. For Function application, choose the application you created earlier
    3. For Function, choose the function you created
    4. Click Create at the bottom of the create panel to save the new connector
  7. The service connector should now be active. Any subsequent email domain log event that fits the filter you specified, should result in a triggering of the function you created, and you should see the invocation and any Python logging output in the function invocation log, and your own HTTP handler should receive the request as intended.

Postback Function Code Samples

We have two OCI Function samples available to cover the various types of OCI Email events. With the Service Connector / Functions integration detailed above, each connector with a Logging source can only pull from a single log at a time. Email domain activity is contained in two different logs (“outboundaccepted” and “outboundrelayed”). If you want to cover all the events listed above in the "Email Domain Log Events and Filtering" table you would need to create two service connectors: one to cover the events in outboundaccepted, the other for events in outboundrelayed.

Log Events Covered
outboundaccepted
  • accept
  • Recipient suppressed ("blocked", that is suppressed because recipient is on suppression list)
outboundrelayed
  • relay ("delivered")
  • bounce (hard or soft)
  • complaint (recipient reported spam)
  • open
  • click