Backup & Restore
ServerlessInbox keeps your mail, contacts and configuration in DynamoDB tables and an S3 bucket that are deliberately kept separate from the rest of your stack. Those data resources are retained: if the ServerlessInbox stack is deleted, they survive the deletion rather than going with it. That separation is what makes recovery possible at all, and it’s the foundation everything on this page builds on.
Recovery has two halves. AWS provides the backup mechanisms — point-in-time recovery for the tables, object versioning for the bucket — and those restore your data into new resources. ServerlessInbox provides the other half: a supported way to tell a running deployment to use a restored table instead of the one it created. Without that second half, a restored table is just data sitting in your account that nothing reads.
What protects your data
Section titled “What protects your data”Each of the five data tables has point-in-time recovery (PITR) enabled. PITR is DynamoDB’s continuous backup: it lets you restore the table’s contents as they were at any moment within the recovery window, which by default extends back 35 days. You don’t schedule anything and there’s nothing to maintain — it’s on from the moment the table is created.
The blob bucket has object versioning enabled. Every overwrite and every delete leaves the previous version of the object in place rather than destroying it, so an object that was accidentally changed or removed can be recovered. Noncurrent versions are not kept forever: a lifecycle rule expires them 35 days after they stop being current, which puts a hard limit on how far back an object-level recovery can reach. A companion rule cleans up expired delete markers.
Both protections are on in your deployment. Point-in-time recovery on the tables and versioning on the bucket are enabled from the moment those resources are created, and the data resources themselves are retained if the stack is ever deleted. There is nothing you need to switch on, nothing to opt into, and no setting you have to verify first — a deployment installed the normal way has all three of these in place, so you can rely on a restore being available from day one.
Restoring a table
Section titled “Restoring a table”A DynamoDB point-in-time restore always produces a new table — it never overwrites the source. This is a good thing for you: the existing table stays fully readable and writable while the restore runs, so you lose nothing by starting one, and you can compare the two before committing to a switch.
Start the restore from the DynamoDB console (select the table, open its Backups tab, and choose Restore in the point-in-time recovery section) or with the CLI:
aws dynamodb restore-table-to-point-in-time \ --source-table-name <existing-table-name> \ --target-table-name <restored-table-name> \ --restore-date-time <timestamp>Use --use-latest-restorable-time instead of --restore-date-time if you want the most recent restorable
point, typically around five minutes ago. The restored table starts in a CREATING state and becomes
ACTIVE when DynamoDB has finished rebuilding it, including its secondary indexes; larger tables take
longer, and AWS does not guarantee a completion time.
The restore brings across the table’s data, its global and local secondary indexes, its capacity settings and its encryption configuration. It does not bring across a set of table-level settings, which you have to reapply yourself on the new table:
- Time to Live (TTL) settings
- Point-in-time recovery — the restored table starts with PITR off, so the new table is unprotected until you enable it
- DynamoDB Streams settings
- Auto scaling policies
- Tags
- IAM policies
- CloudWatch metrics and alarms
Reapply these before you put the restored table into service. TTL and PITR matter most: without TTL the table stops expiring records that are meant to expire, and without PITR you have no continuous backup of the table you just recovered into. The settings on the original table are the reference for what the new one should look like — read them from the source table and mirror them.
Reference: Restoring a DynamoDB table to a point in time.
Pointing the deployment at the restored table
Section titled “Pointing the deployment at the restored table”This is the step that makes a restore useful, and it’s the part that is specific to ServerlessInbox.
Your stack maintains one SSM parameter per table role, and the value of that parameter is the ARN of the table the deployment actually uses for that role. When the stack is first deployed, each parameter is set to the ARN of the table the stack created. To redirect a role at a restored table, you overwrite the parameter with the restored table’s ARN. Nothing else needs to change.
The parameters are named:
/serverlessinbox/<stack-name>/data-override/<role>where <stack-name> is the name of your ServerlessInbox CloudFormation stack and <role> is one of the
five table roles:
configTableitemTablechangelogTableauditLogTablemailFeedbackTable
So for a stack named serverlessinbox you would write the restored ARN to
/serverlessinbox/serverlessinbox/data-override/itemTable to redirect the item table role:
aws ssm put-parameter \ --name "/serverlessinbox/<stack-name>/data-override/itemTable" \ --type String \ --value "arn:aws:dynamodb:<region>:<account>:table/<restored-table-name>" \ --overwriteThe change does not take effect when you write the parameter. The deployment reads these parameters during a stack update, so the redirection happens on the next stack update — either an update you install yourself from the Infrastructure page in the admin UI, or the next automatic one. Until that update runs, the deployment keeps using whatever table it resolved last time. Plan on triggering an update deliberately rather than waiting, so that you know exactly when the switch happened.
There is a safety check on that update, and it is worth understanding before you rely on it. During the
update, the deployment inspects every role whose parameter value differs from the table the stack itself
owns, and confirms that the table named by the override actually exists and is in the ACTIVE state. If it
doesn’t exist, the update fails, naming the role — for example
itemTable: override table arn:aws:dynamodb:… does not exist. If it exists but isn’t ACTIVE yet, the update
fails with the role and the status it found instead. This is intentional: a typo in an ARN, or an update
started while a restore is still rebuilding indexes, stops the update rather than bringing the deployment up
against a table that isn’t there. If you see one of these failures, fix the parameter value or wait for the
restored table to reach ACTIVE, then run the update again.
A parameter whose value still matches the stack’s own table is left alone and checked against nothing — the check only applies where you have actually overridden something.
Returning to the stack’s own table
Section titled “Returning to the stack’s own table”Running on an overridden table is a recovery state, not a destination. The table the stack owns is the one its lifecycle manages, and you should plan to return to it once the incident is over.
The move back has three steps, in order:
- Copy the data back. Get the contents of the restored table into the table the stack owns. Nothing in ServerlessInbox does this for you — use whatever bulk copy approach suits the table’s size, and do it at a point where you can tolerate the write traffic.
- Put the original ARN back in the parameter. Overwrite the same
/serverlessinbox/<stack-name>/data-override/<role>parameter with the ARN of the stack’s own table. If you didn’t record it before you changed it, the parameter’s history in SSM has the previous value. - Update the stack. As with the switch away, the change only takes effect on the next stack update.
Once that update has succeeded, the deployment is reading and writing the stack’s own table again and you can delete the restored table when you’re confident you no longer need it.
Restoring an object in the bucket
Section titled “Restoring an object in the bucket”Object recovery in the blob bucket runs entirely through S3’s own versioning — there is no ServerlessInbox step involved, and nothing to redirect.
In the S3 console, open the bucket, enable Show versions, and locate the object. If the object was
deleted, what you’ll find is a delete marker sitting on top of the earlier versions; deleting that marker
makes the most recent real version current again and the object reappears. If the object was overwritten,
copy the version you want back over the current one, or download it and put it back. The CLI equivalents are
aws s3api list-object-versions to find the version IDs and aws s3api get-object --version-id or
aws s3api delete-object --version-id to act on a specific one.
The limit on all of this is the noncurrent-version window described above: 35 days. A version that stopped being current more than 35 days ago has been expired by the lifecycle rule and is gone. If you discover a problem with an object, check for recoverable versions early rather than assuming they’ll still be there.
Adding AWS Backup
Section titled “Adding AWS Backup”PITR and versioning cover the common case, but both are bounded at 35 days and both keep the copy inside the same account and region as the original. If you need longer retention than that, or a copy that survives the loss of the account itself, the data tables and the blob bucket are ordinary DynamoDB and S3 resources in your own account — so you can add them to an AWS Backup plan the same way you would any other resource, with scheduled backups, long retention, and cross-account or cross-region copies. Nothing about how ServerlessInbox creates those resources stands in the way, and a backup plan sits on top of PITR and versioning rather than replacing them.
The practical question is how to select exactly the data tier and nothing else, and the resource tags
answer it. Every resource in the data stack carries serverless-inbox:component with the value data,
alongside serverless-inbox:product, serverless-inbox:root-stack (the name of your main ServerlessInbox
stack), serverless-inbox:stack, serverless-inbox:version and serverless-inbox:deployer_type. A
tag-based backup selection on serverless-inbox:component = data together with
serverless-inbox:root-stack = your stack’s name picks up the five tables and the bucket, and only those —
and it keeps picking them up if the data tier ever gains a resource, because the selection matches tags
rather than a list of ARNs you have to maintain.
Be deliberate about the cost. AWS Backup is billed separately by AWS: you pay for the backup storage you accumulate, and you pay again when you restore. A plan with long retention over a growing mail store is a real, recurring line on your bill, which is why this is something you choose rather than something that is on by default. Decide the retention you actually need before you write the plan, not after the first invoice.
This is your own configuration to create and to maintain. ServerlessInbox does not create a backup plan, does not manage one you create, and will not notice if yours stops running — monitoring the plan is part of owning it. Native support for this may be added to the product in the future, but there is nothing to wait for today.
Limits
Section titled “Limits”Restoring into a different deployment is not supported. Data restored from one ServerlessInbox deployment belongs to that deployment, and pointing a different deployment at it is outside what the product supports. Mail clients track their synchronization position against the state counters in your deployment’s changelog, so a deployment and a data set that don’t belong together will break client sync in ways that aren’t recoverable by re-syncing.
Everything on this page is about restoring a deployment’s data back into that same deployment. Migrating mail between deployments is a different problem and needs a different approach.