Compatibility
What the engine serves, to whom, and where it answers differently from Amazon SES.
The SES v2 API
On [ses] listen, over HTTPS, signed with AWS Signature Version 4 by an
access key from nitromta access-key create. The engine serves these
operations; any other is refused BadRequestException, which no SDK
retries.
| Area | Operations |
|---|---|
| Sending | SendEmail (simple, raw and template content), SendBulkEmail (template content) |
| Templates | CreateEmailTemplate, GetEmailTemplate, UpdateEmailTemplate, DeleteEmailTemplate, ListEmailTemplates |
| Identities | CreateEmailIdentity (domains, with Bring Your Own DKIM), GetEmailIdentity, DeleteEmailIdentity, ListEmailIdentities, PutEmailIdentityDkimAttributes, PutEmailIdentityDkimSigningAttributes, PutEmailIdentityFeedbackAttributes, PutEmailIdentityMailFromAttributes |
| Configuration sets | CreateConfigurationSet, GetConfigurationSet, ListConfigurationSets, PutConfigurationSetReputationOptions, PutConfigurationSetDeliveryOptions, CreateConfigurationSetEventDestination, GetConfigurationSetEventDestinations, UpdateConfigurationSetEventDestination, DeleteConfigurationSetEventDestination |
| Tenants | CreateTenant, GetTenant, ListTenants, CreateTenantResourceAssociation, DeleteTenantResourceAssociation, ListTenantResources, ListResourceTenants |
| Reputation | GetReputationEntity, UpdateReputationEntityCustomerManagedStatus |
| Account | GetAccount |
| Suppression list | PutSuppressedDestination, GetSuppressedDestination, DeleteSuppressedDestination, ListSuppressedDestinations |
| Dedicated IPs | CreateDedicatedIpPool, PutDedicatedIpInPool, GetDedicatedIp, GetDedicatedIps |
Within those operations, a few fields the engine does not act on are refused by name, never ignored:
- Simple content takes no attachments (send them in raw content), and
is sent as UTF-8: a part may name no charset,
UTF-8orUTF8, orUS-ASCIIorASCIIwhen its text is ASCII; any other is refused. - Template content is a template the account keeps, by its
TemplateNameor by aTemplateArnof this account and region, or the request's ownTemplateContent. Mail sent for a tenant uses a kept template only once it is associated with the tenant (CreateTenantResourceAssociation), as SES has it. A template's parts are filled by{{name}},{{{name}}}and dotted names such as{{user.name}}alone: a template with a block ({{#if}},{{#each}}), a partial or a comment is refused as it is created. A kept template's parts come to at most 512 KiB, an account keeps at most 10,000, and tags are not kept on them. SendBulkEmail's rendered messages together come to at most the largest request the API takes: 54,381,912 bytes, from[submission] max_message_bytesat its default. An entry past that is answeredMESSAGE_REJECTED, and so is every entry after it, while those before it are sent, so a very large batch is best split.PutConfigurationSetDeliveryOptionstakes the pool alone: aTlsPolicyofREQUIREandMaxDeliverySecondsare refused, as transport security is the installation's ([delivery] require_tls, MTA-STS and DANE) and a message's lifetime is[submission] lifetime_seconds.
An access key sends unless it was made with --manage-only, and manages
the account through the API only when made with --manage or
--manage-only. A key or login scoped to identities or tenants sends as
those alone.
Raw content may carry X-SES-CONFIGURATION-SET, X-SES-TENANT and
X-SES-MESSAGE-TAGS header fields, which the engine honours and removes,
as SES does; it removes X-SES-SOURCE-ARN, X-SES-FROM-ARN and
X-SES-RETURN-PATH-ARN too.
The SNS subset
On the same listener, at its root: CreateTopic, GetTopicAttributes,
Subscribe, ConfirmSubscription, Unsubscribe and
ListSubscriptionsByTopic, for HTTPS subscribers, enough for an application
that receives SES's events through SNS to receive the engine's. A new
subscriber is sent its SubscriptionConfirmation, and every message is
signed with SignatureVersion 2 under [sns] key, whose certificate the
listener serves for subscribers to verify against.
Events
Through a configuration set's event destinations, the engine publishes
SES-shaped events to SNS topics: Send, Delivery, DeliveryDelay,
Bounce and Complaint, as SES writes them, with their tags.
Native subscriptions (create_subscription on the admin socket) post a
configuration's events to a URL in signed batches, of the kinds named:
accepted, delivered, captured, deferred, bounced, uncertain,
expired, suppressed, cancelled and complained. A batch that fails
is retried, and parked once its retries are spent, to be replayed with
nitromta replay. The same event can be posted again after a lost
response, under the same ID, so a subscriber must treat it as the same
event.
SMTP
Submission, on [submission] listen: STARTTLS, which is required before
AUTH PLAIN; PIPELINING, SIZE, 8BITMIME and ENHANCEDSTATUSCODES.
The login decides the account, and the message's one From address the
identity it is sent as.
Delivery: to each domain's MX hosts, over STARTTLS. TLS is required by
default ([delivery] require_tls = true): mail to a receiver that does not
offer STARTTLS is tried again until it expires; set it to false to
deliver in the clear to such receivers. MTA-STS (RFC 8461) and DANE (RFC
7672) are on by default; a domain's policy decides which exchanges it may
go to and how their certificates must be verified.
Each IP sends with its own HELO name, and mail is signed with DKIM as its
identity says.
Feedback, on [feedback] listen, the return path domain's MX: delivery
status notifications (RFC 3464) and complaint reports (RFC 5965), matched
to the mail they are about by its return path. By default a report that
matches the engine's own mail suppresses its recipient and counts toward
its IP's warmup; with [feedback] trusted = "authenticated", only one
whose DKIM signature verifies for a reporter enrolled in
[feedback] reporters does, and any other is recorded as an event alone.
The SDKs, tested
cargo xtask sdk runs the official SES v2 SDKs, pinned, against the
engine: @aws-sdk/client-sesv2 and Nodemailer's SES transport in
JavaScript, boto3 and django-ses in Python, and aws-sdk-sesv2 in Ruby.
sdk/README.md lists the scenario each runs.
Where the engine differs from SES
- Identities.
CreateEmailIdentitycreates domain identities alone, each with its own DKIM key (Bring Your Own DKIM); Easy DKIM and email address identities are refused, and an address at a verified domain sends as that domain. The engine verifies each such identity, and each MAIL FROM domain put through the API, by looking up its records with its own resolver: a pending check at once and then less often, a verified one daily. An identity put through the admin socket is not checked;nitromta doctorchecks it instead. - Event fields not given:
mail.sourceArn; a delivery's and a bounce'sremoteMtaIpandreportingMTA; aDeliveryDelay'sexpirationTime; a complaint'suserAgentandarrivalDate; themail.tenantobject; and the tagsses:source-tls-versionandses:outgoing-ip. Every other member SES writes, the engine writes the same way, checked against events recorded from SES (fixtures/ses). - A tenant not associated with the identity it sends as is refused 400
BadRequestExceptionopening "Tenant not associated with resources", where SES answers 403AccessDeniedException. - Open and click tracking are not given: a configuration set's
TrackingOptionsis refused, and there are noOpenorClickevents. - No request is made idempotent. As with SES, a
SendEmailretried after its answer was lost sends a second message. - 8BITMIME is not negotiated on delivery. Content is sent as it was submitted, so a message with 8-bit content should be submitted with a transfer encoding.
- One host. The engine runs on one host, with its store on that host's
disk. There are no regions;
[ses] regionis the one it signs and answers as.