2018 retrospective
Time passes and it’s easy to forget about all the cool stuff you did or lessons you learned. But source control makes it easy to go back and remind yourself, so I thought I’d take a moment to look back and summarise my past year in code.
January
January kicked things off with some improvements to our Redis abstraction and a number of small fixes to the Firefox Accounts metrics infrastructure.
In mozilla/fxa-auth-server#2254, I changed the serialisation format of the session token data that we store in Redis, reducing the average payload size by about 16%. The approach I took was to use arrays with fixed indices so that we could stop paying for property keys. I also investigated storing gzipped data instead, but felt that the loss of eyeballability of the data at rest wasn’t worth the ~25% reduction in size it would have garnered.
That work was then continued in mozilla/fxa-auth-server#2257, where I added code to prune expired session tokens from Redis. Together, these two changes solved a real operational problem we were suffering from, where we had to keep scaling up the size of our Redis instance and our costs were going up commensurately.
In mozilla/fxa-amplitude-send#38, I wrote a script to import FxA marketing events to Amplitude, so they could be analysed alongside the rest of our product metrics. It entailed some fairly hairy regex work and, as ever, we made sure not to send personally-identifiable information to 3rd-party services. That extends as far as hashing our own uid prior to sending, to make it impossible for anyone to cross-reference metrics data with information from our production database. We do this even though production db access is locked down to just a few members of our Ops team.
In all, there were 16 notable changesets in January:
- break(output): clearer delineation of change types in the summary
- fix(tests): fix failing amplitude geolocation tests
- fix(tests): fix failing geolocation tests
- fix(metrics): ditch the non-useful performance flow events
- feat(redis): eliminate property names from redis-stored tokens
- fix(tests): update tests to use a working ip address
- feat(api): return location object directly instead of in a promise
- fix(zlib): flush the zlib decompressor
- feat(marketing): add script for sending marketing events from csv data
- fix(tests): ensure consistent Date.now() in _calculateQueueTime test
- feat(redis): prune expired session tokens from redis
- fix(scripts): add SET NAMES to reverse migration boilerplate
- fix(redis): pack redis tokens inside db.deleteSessionToken
- feat(devices): return session token id from deleteDevice
- fix(metrics): ensure amplitude events always have a metrics context
- fix(redis): delete session tokens from redis in db.deleteDevice
February
In February I went on holiday to Barbados!

But before that, I also managed to get some coding done. Again it was mostly metrics-related, althought none of it hugely interesting.
In mozilla/fxa-amplitude-send#47,
I wrote a script to import
a Sync complete event to Amplitude
from Firefox telemetry data.
But, as it stands,
we still haven’t enabled this in production
because it generates a lot of events
and needs more metadata
to be really useful.
And in mozilla/fxa-auth-server#2282, I eliminated some duplication that achieved a net deletion of 620 lines of code. I love deleting code even more than I love writing it!
February saw 20 changesets in total:
- chore(ci): stop setting USE_REDIS in the test invocations
- fix(metrics): emit amplitude click events earlier
- chore(code): eliminate duplicate pool and db modules
- chore(emails): remove all verification reminder code
- fix(cad): hide the success message after direct navigation
- fix(navtiming): ensure negative values aren’t reported
- fix(api): make authentication required on GET /account/profile
- fix(walk): handle stream errors sanely
- fix(errors): ensure that toError always behaves sanely
- fix(docs): document the pause and resume functions on event emitters
- fix(script): show sub-headings in git tag comment
- chore(emails): delete bin/mailer_server.js
- chore(logging): downgrade redis.watch.conflict to warning level
- feat(sync): add a –report-only command-line switch to aid debugging
- feat(sync): more detailed logging of the final event counts
- fix(metrics): treat enter-email as an “auth” view for flow events
- fix(redis): delete clashing tokens from redis in createSessionToken
- chore(lint): fix lint errors
- fix(sync): time should be epoch-milliseconds
- feat(sync): send sync_complete event to amplitude
March
March saw fixes for a couple of interesting security vulnerabilities.
In mozilla/fxa-auth-server#8da511c8 and mozilla/fxa-content-server#e73873cd, I added safeguards to prevent unescaped input reaching our back-end via the user agent string. The user agent parser we use includes some regexes that propagate parts of the input string to the output. The new safeguards ensure that such input gets dropped at point of entry. Unfortunately the contextual discussion for this vulnerability is not public, but people with appropriate permissions can read more in bug 1445629.
In mozilla/fxa-auth-server#2368,
I wrote a SafeUrl class
to ensure our communication
with other back-end services
did not leave us open
to a node.js request-splitting exploit.
Also related to this
were some small validation fixes
in mozilla/fxa-auth-server#2359
and mozilla/fxa-content-server#5996.
The discussion around these changes
can be found in bug 1447452.
Elsewhere, in philbooth/bfj#e2e320db, I added a nice feature that allows JSON streams to be asynchronously parsed for interesting subtrees, without needing to load the entire tree into memory. I blogged more about this feature here.
March changesets:
- feat(script): rough outline for driver/import script
- fix(navtiming): stop assigning to w.p.t.navigationStart
- feat(settings): basic ini files
- fix(docs): update dev-process docs with new meeting schedule
- fix(docs): expand on the release-tagging process
- fix(metrics): ensure service is set when possible on amplitude events
- feat(metrics): emit view, engage & submit events for CAD
- fix(emails): prevent unsafe content from reaching rendered email body
- feat(walk): add support for NDJSON streams
- fix(unpipe): prohibit unpipe from setting the ndjson option
- feat(fxa): map content server flow data to amplitude
- fix(metrics): include full version information in event data
- fix(metrics): include full version information in amplitude event data
- fix(server): validate ip addresses before use
- chore(deps): update ip-reputation-js-client
- fix(server): validate ip addresses before setting them on request object
- fix(server): ensure unsafe input doesn’t leak from user-agent strings
- fix(sessions): only return major rev for browser version
- feat(match): implement a streaming match api
- fix(metrics): ensure CAD view and engage events are correct
- chore(db): prevent the possibility of future url-injection bugs
- fix(metrics): use $append on the experiments user property
- feat(metrics): add user properties for active device counts
- fix(metrics): emit route flow events from more endpoints
- fix(metrics): ensure CAD view and engage events are correct
- feat(metrics): add an email_domain property to amplitude click events
- fix(metrics): count 28 days per metric month
- break(walk): distinguish between syntax and operational errors
- feat(streams): expose a highWaterMark option
- chore(docs): note the end of node-4 maintenance
- break(eventify): distinguish between syntax and operational errors
April
In April, I started work on mozilla/fxa-email-service, which would prove to be my main focus for most of the year. It was a rare opportunity for some greenfield development on Firefox Accounts and a chance for me to drive a project from start to finish.
The rationale behind the work was to decouple our authentication server from Amazon SES. That would open up the possibility of richer bounce-handling behaviour and let us fall back to alternative email providers for problematic domains or addresses.
The most enjoyable aspect for me was that it was to be written in Rust. Learning Rust was the best part of my job in 2018 and a direction I’m keen to pursue further in the future.
Apart from that, there was one interesting feature in mozilla/fxa-auth-server#2401, where I added logic to check the available budget for Amazon SNS before deciding whether to display our “Connect Another Device” form after a user signs in to FxA. We’d found ourselves running out of SMS budget in production a few times, which led to a poor user experience when the Firefox install link that we send out via text message failed to send. Detecting that state ahead of time allowed us to provide a more coherent user experience.
April changesets:
- chore(tests): delete unused var
- feat(match): pass a depth argument to selector predicates
- chore(tests): migrate from tap to mocha+chai
- feat(email): define array of popular email domains
- chore(emails): use popular email domain list from fxa-shared
- chore(emails): use popular email domain list from fxa-shared
- fix(server): fix broken require path
- fix(metrics): add locale to flow events
- feat(metrics): extract common code for emitting amplitude events
- refactor(metrics): use boiler-plate amplitude code from fxa-shared
- refactor(metrics): use boiler-plate amplitude code from fxa-shared
- fix(metrics): stop using user-agent string in flow id check
- fix(metrics): stop using user-agent string in flow id check
- chore(logging): use a less confusing op on flow event errors
- chore(tests): remove duplicate mocking code
- chore(scripts): remove archived repositories from milestone list
- feat(sms): query the available budget in /sms/status
- fix undefined dereference in getSource
- fix(server): fix undefined dereference
- chore(scripts): add fxa-email-service to repo list
- feat(api): implement a basic /send endpoint
- feat(config): add configuration
- feat(sms): enable permissions for SMS budget checks
May
In May, I moved all of my personal repositories to GitLab. As a proponent of open-source software, I like the idea of hosting my own open-source code on a service that is itself open-source and I’d wanted to move for a while, but the inertia of already having everything on GitHub was difficult to get past. Rumours of a Microsoft buyout were a sufficient nudge though and, as it turned out, migrating was really easy. GitLab pulled everything across, including issues and so on, at the touch of a button. Ultimately I plan to move to a self-hosted GitLab instance running under this domain, but that’s a topic for another blog post.
On the work front, May was all about the email service. Looking back at some of those changes now, my inexperience with Rust at the time is clear to see. But the broad direction stands up well, such as mozilla/fxa-email-service#34 where I leaned on the trait system to implement a Sendgrid provider that had interface-compatibility with our SES provider.
These were the changesets from May:
- refactor(settings): extract common functions for deserialization
- chore(project): add the MPL
- feat(ses): implement ses-based email sending
- fix(metrics): remove old flow signature fallback code
- fix(metrics): remove temporary flow validation fallback code
- fix(client): improve messaging before delete account
- chore(logging): downgrade location translation error to warning
- feat(db): check the emailBounces table before sending email
- fix(metrics): don’t emit route flow events for 404s
- fix(logging): log successful sms budget checks
- feat(docs): add a readme doc
- fix(docs): fix devices validation output of the doc generator
- refactor(metrics): move amplitude email types back here from fxa-shared
- fix(nsp): fix nsp warnings
- refactor(metrics): pull email type definitions back up to the caller
- refactor(metrics): move amplitude email types back here from fxa-shared
- fix(sms): follow documented conventions for AWS GetMetricStatistics call
- fix(db): add missing fields/values to auth_db::BounceRecord
- feat(queues): handle SES bounce, complaint and delivery notifications
- fix(db): fix broken BounceSubtype deserialization
- fix(errors): include more useful messages in wrapping errors
- feat(db): implement AuthDb::create_bounce
- chore(queues): split out a separate process for queue-handling
- feat(sendgrid): implement basic sending via sendgrid
June
June was a difficult month, as my Dad’s health deteriorated rapidly and he finally lost his long, grim battle against a cancer that started in his pancreas, then spread to his liver, stomach and lungs. It seems strange to write about that in a blog post about programming, but it felt strange at the time to just continue on with work and side-projects, while more important stuff was happening in real life.
That dissonance is reflected in the changesets for June, which were mostly trivial and relatively few in number:
- fix(errors): return JSON payloads for error responses
- fix(sendgrid): return message id from the sendgrid provider
- feat(tokens): define token trait and grapheme implementation
- refactor(tokens): make terminates non-optional
- feat(tokens): implement the word type
- fix(tokens): ensure word manipulation cascades to graphemes
- fix(validation): relax the sendgrid API key validation regex
- chore(errors): don’t serialize nulls in error payloads
- chore(code): eliminate some if-lets in favour of combinators
- fix(bounces): ensure db errors don’t get converted to 429 responses
- fix(eventify): don’t serialise NaN or infinities
- fix(script): make it work on GNU sed
- feat(metadata): store caller-specific metadata with the message id
- feat(metadata): send message metadata in outgoing notifications
- refactor(project): push core code into a common lib
- refactor(db): hide queues db access behind bounces interface
- fix(queues): parse notifications synthesized from sendgrid events
- fix(metrics): prevent reset-password from clobbering mixed-in events
- feat(scripts): add boilerplate to detect missing migrations
- refactor(tokens): extract word logic to generic ParentToken struct
- fix(tokens): push appropriate terminates value on to child tokens
July
In July, work continued on the email service and started on a Rust port of the Firefox Sync storage server.
An interesting change related to the former was the implementation of dynamic email config in mozilla/fxa-auth-server#2535. This was a big deal because previously all our configuration data was static and required a deployment by the Ops team for us to make changes. Co-opting Redis for this was something of an ad hoc approach, but worked well and allowed us to make rapid config changes such as diverting email for particular users via a different provider, after they reported that they weren’t receiving any email from Firefox Accounts at all.
Amongst the usual array of other fixes,
in mozilla/fxa-auth-server#2550
I addressed a curious 500 error
that was reported by Sentry.
It indicated that somehow,
one of the session token objects
cached in Redis
was invalid JSON when we came to unpack it.
After failing to identify any possible ways
for such data to arrive in the data store,
and bearing in mind there had been a single case
of this error
across hundreds of millions of session tokens,
I chalked it up to “operational weirdness”
then added some logic to purge the offending data
if it happened again.
The error never recurred
and it remains an unsolved mystery.
There were 27 changesets in July:
- chore(code): jiggle some stuff around because ocd
- feat(tokens): implement encapsulated token parsing
- refactor(tests): change some test names
- chore(tests): assert that empty lines are parsed correctly
- feat(tokens): implement the paragraph type
- fix(scripts): update references to bin names with
fxa-email-prefix - feat(docs): generate developer docs with rustdoc
- chore(docs): automatically publish rustdoc output to github pages
- fix(scripts): stop gh-pages script from failing for pull requests
- fix(scripts): don’t try to deploy docs if they haven’t changed
- fix(metrics): don’t force utm_source=email on links in emails
- refactor(types): extract the EmailAddress type to its own module
- fix(docs): better docs for the settings module
- refactor(tokens): use internal iteration for child tokens
- fix(metrics): force utm_source=email when signing in from CAD
- feat(metrics): add event properties for email sender and service
- feat(metrics): add amplitude event properties for email service/sender
- fix(email): gracefully handle errors from fxa-email-service
- fix(deploy): pin to a known compatible rust version in docker and ci
- fix(email): fix broken X-SES-CONFIGURATION-SET header
- feat(email): add a service property to the X-SES-MESSAGE-TAGS header
- fix(package): fixes for npm security audit
- feat(email): read live email-sending config from redis
- fix(tests): move local utils tests so they get run by npm t
- feat(settings): enable the default provider to override requests
- feat: handlers and dispatchers for get_bso and put_bso
- fix(redis): recover from invalid token JSON in Redis
- fix: turn on cargo fmt in CI and fix fmt errors
August
During August, I finished off the work for dynamic email configuration in mozilla/fxa-auth-server#2571, mozilla/fxa-auth-server#2574 and mozilla/fxa-auth-server#2576.
In mozilla/fxa-auth-db-mysql#392,
I wrote a script
to run EXPLAIN checks
of our MySQL stored procedures
in CI.
This was an idea I had
during the postmortem meeting
for a Firefox Accounts database outage
that we suffered in production.
When I suggested it during the meeting
I got some doubtful responses,
but I decided to try it out anyway
and it turned out pretty well.
You can read more about it
in my blog post
on the subject.
I also started working on Hawk authentication for the Rust syncstorage port in mozilla-services/syncstorage-rs#20. It was the first time I’d really looked at the Hawk protocol in depth, so there were a lot of kinks to iron out during code review and the PR didn’t actually land until September.
August changesets:
- fix(tests): stop creating a fresh DBManager on every request
- feat: stub out some more endpoints
- fix(email): ensure email-service errors fail the call to sendMail
- feat: add handler stubs for collection endpoints
- fix(email): JSON.parse live email config after reading from redis
- feat(email): write live email-sending config to redis
- fix: rip out the DBExecutor code
- Small improvements for the email-config script
- Fixes for email address validation
- fix(metrics): stop sending unused performance flow events
- feat(docs): add machine-readable validation regexes for fxa metrics
- Debigulate flow_events
- feat: add handler stub for DELETE /{uid}
- fix(devices): used cached devices property during requests
- chore(db): stop calling the upsertAvailableCommands procedure
- fix(scripts): fix the broken api docs generator script
- fix(devices): check token.deviceAvailableCommands before dereferencing
- feat(tests): assert that validation regexes match docs
- feat: define and mock a db trait
- feat(scripts): add script to automate EXPLAIN checks
- chore(email): force value to boolean in account deletion check
- fix(scripts): remove nonsense (but harmless) comparison of bool to -1
- feat(scripts): delete flow.begin events after processing
- fix(logging): log errors when reading/parsing live email config
- feat: implement hawk authentication
September
We had something of an indian summer in September and it was proper beach weather where I live on the south coast of England:

For Firefox Accounts, September saw deployment of the email service to production. The deployment went smoothly and over following weeks we were able to use the work I’d done on dynamic email configuration to stage a gradual rollout to increasing percentages of our userbase.
September changesets:
- chore(tests): switch from insist to chai for assertions
- fix(metrics): send events from /metrics-flow to amplitude
- fix(code): fix some lgtm analysis warnings
- feat(docs): include more dev/setup info in the readme
- feat(deploy): build a zip archive for lambda deployment
- chore(repos): add the email event proxy to repo list
- feat: implement Deserialize for the master_secret setting
- fix(l10n): ensure the correct language is selected in localizeTimestamp
- fix(metrics): prohibit overwriting stashed metrics context
- feat(scripts): force registry links in shrinkwrap to use tls
- feat(scripts): force registry links in shrinkwrap to use tls
- feat: implement the /configuration endpoint
- chore: add doc comments for Hawk authentication
- Treat empty environment variables as unset
- fix(config): ignore empty environment variables
- refactor(tokens): replace Token trait with an enum
- fix(metrics): stash metrics context during the account reset flow
- fix(scripts): make tls-shrink script portable
- fix(scripts): make tls-shrink script portable
October
In October, I dedicated a week of leave to working on side-projects that I’d been neglecting for some time. Mostly I worked on pbvi, a text editor that I decided to build when I started learning Rust. I’m not sure if I’ll ever finish it, but it’s been a fun project and a really useful playground to help me learn the language. I also published my first Rust crate, unicode-bom, which is just a tiny little dependency that detects the unicode byte-order mark in a file or byte array. And I spent a little bit of time on fxabot, a modified Hubot instance that can broadcast deployment info to our IRC and Slack channels.
Back at work,
with the email service deployed,
it was an opportunity to pick up
a number of lower-priority house-keeping tasks
that had been annoying me for a while.
For instance,
in mozilla/fxa-email-service#196
I refactored from raw strings
to a strong EmailAddress type,
in mozilla/fxa-email-service#200
I automated the (de)serialisation of data
at the boundary of our Redis abstraction
and in mozilla/fxa-email-service#213
I tidied up the directory structure a bit.
It was also a chance
to lay down some groundwork
for opening up the email service
to other teams,
by starting the process of
migrating away from
the auth server’s database
in mozilla/fxa-email-service#203.
October changesets:
- chore(sms): log JSON-serialised result if we fail to parse max spend
- refactor(redis): extract redis code to a dedicated db module
- fix(metrics): remove metricsContext from deprecated routes
- feat(tokens): notify parent of child deletion and reinstatement
- refactor(tokens): move logic for sibling nodes to its own module
- refactor(tokens): move logic for child nodes to its own module
- refactor(tokens): move logic for parent nodes to its own module
- refactor(tokens): move logic for values to its own module
- feat(tokens): notify parent of child replacement
- Phil Booth’s avatar feat(cursor): implement a basic cursor with line and grapheme movement
- refactor(tokens): simplify token reads with TokenArena::read
- fix(cursor): ensure sane cursor-handling of newline characters
- fix(tokens): ensure terminates flag cascades to all descendants
- feat(cursor): maintain an ideal grapheme index while moving lines
- feat(cursor): implement line-end stickiness when moving lines
- feat(cursor): implement basic word movement
- fix(cursor): include current word in reverse movement when not at start
- feat(cursor): implement word-end movement
- fix(cursor): include punctuation in word movement
- feat(cursor): implement basic paragraph movement
- refactor(cursor): eliminate some common boilerplate and reorder some methods
- feat(file): implement basic file reading
- refactor(tokens): make arena reference-counted member instead of static
- refactor(types): prefer EmailAddress type to raw strings
- fix(ci): only build deployment artifacts for tags
- refactor(bounces): pull all bounce/complaint code into one module
- feat(db): serialize to JSON on write and deserialize from JSON on read
- refactor(redis): stop treating no data as an error in the getters
- fix(email): handle the new error structure from fxa-email-service
- fix: return an array from GET /info/quota
- chore: deploy docs to gh-pages branch
- fix: use posix-friendly logical and in docs script
- fix: use single = for equality test in docs script
- fix(metrics): stop sending metrics context to deprecated endpoints
- fix(email): include data from headers in email sent events
- feat(db): write bounce and complaint records to our own db
- chore(tests): add test coverage for the order of delivery problems
- fix(mem): ensure emailBounces are stored most-recent first
- fix(email): ensure mock senders take precedence over the email service
- feat: map db and hawk errors to a common top-level error type
- refactor: convert validation errors to use failure
- fix(metrics): ensure email events use stashed flow data where applicable
- fix(metrics): ensure metrics context is propagated from /account/reset
- chore(api): remove metrics context data from deprecated endpoints
- feat(metrics): add code and config for email service notification queue
- fix(ci): update travis to use oauth server in auth server repo
- fix(logging): log the reason for account deletions
- refactor: map to failure errors in X-Weave-Timestamp middleware
- fix(metrics): use correct format for email service notifications
- fix(queues): fix the serialized format of outgoing notifications
- fix(code): implement AsRef
to expose cheaper &str access - feat(scripts): add ROW_COUNT() checks to the procedure-linting script
- fix(scripts): stop the explain script tripping over git grep colours
- fix(tests): remove assertions of profileChangedAt property
- refactor(project): tidy up the directory structure a bit
- feat(settings): extract provider type to a fully-fledged enum
- chore(errors): make email-sending errors a 422 for new addresses
- feat(scripts): add fxa train-reporting commands
- feat(scripts): add nominate command
- fix(bot): connect to IRC
- feat(scripts): add command to look up by date string
- fix(scripts): consistent error handling
- fix(scripts): gracefully handle recent lookups by date
- chore(deploy): prepare for side-by-side irc/slack deployment
November
In November, traffic to the email service reached 100%, marking the end of our phased rollout to production. It was another month of refactorings and small-ish fixes.
In mozilla/fxa-email-service#246,
I wrote an automated release-tagging script
for the email service.
It borrows from some prior art
in my personal release-tagging script,
philbooth/please-release-me,
but is tailored towards
the particular conventions
followed by the FxA repos.
Because we use structured commit messages,
it’s easy to employ a combination of
git log, cut and awk
to generate a readable change log
for each release.
The version number
can be bumped
in Cargo.lock and Cargo.toml
using sed,
then it’s just a case
of committing the changes
and creating a tag.
In mozilla/fxa-email-service#236, I paid some much-needed attention to error-handling in the email service. It had grown organically into a somewhat tangled mess and was a source of irritation, so I was greatly pleased to clean and simplify it.
In mozilla/fxa-email-service#249,
mozilla/fxa-email-service#255
and mozilla/fxa-email-service#256,
I refactored some settings
from strings to enums.
This work included
a nice little enum_boilerplate! macro
to reduce duplication in the resulting code.
I absolutely love the macro system in Rust,
something that you can read more about
here.
November changesets:
- refactor(errors): stop exposing AppErrorKind directly from AppError
- fix(errors): reinstate bounce error failures/messaging
- fix(email): fix sentry errors in prod
- feat(types): ignore display name part when parsing email addresses
- feat(bounces): use timestamps from ses instead of the current time
- fix(settings): fix panic when sentry is disabled
- refactor(api): reduce the number of different error kinds we return
- feat(logging): enable sentry
- fix(metrics): generate an amplitude deviceId in GET /metrics-flow
- fix(tests): reinstate accidentally-disabled server tests
- fix(prefs): point activity stream at FXA_ENV
- chore(tests): make geolocation assertions more robust
- fix(settings): fix panic when sentry is disabled
- refactor(api): reduce the number of different error kinds we return
- fix(ses): ensure html part gets charset=utf-8 content type
- refactor(settings): promote Env to an enum from a wrapped string
- fix(validation): accept emails containing an apostrophe on the front-end
- Two tiny tweaks for Sentry
- chore(build): lower the debug level to decrease binary size
- fix: return a 415 for unsupported content-type headers
- refactor(settings): promote LogFormat and LogLevel to enums
- fix(metrics): emit login complete amplitude event after reset complete
- feat(scripts): a bunch of changes to the lint script
- refactor(settings): implement Default in enum_boilerplate!
- fix(lambda): stop swallowing internal errors
- fix(metrics): ensure email sent amplitude events include device id
- feat(metrics): add deviceId to the resume token
December
December was pretty quiet, because of the Mozilla All Hands and then the holidays.
In mozilla/fxa-email-service#261,
I implemented a serializable regex type.
And in actix/actix-web#637,
I contributed a fix
to support custom values
for the Content-Type header.
On a personal note, I was really happy to study and pass my WSET level 2.
December changesets:
- fix(errors): convert ses invalid domain error into a 400
- feat(types): implement a serializable regex type
- feat(code): update to rust 2018
- fix(scripts): ignore merge commits when generating changelog
- Support custom content types in JsonConfig
- fix(scripts): fix lint errors in marketing scripts
- feat(scripts): convert the sync import scripts to be docker-friendly
- feat(metrics): add country and region to activity and flow events
- feat(metrics): add country and region to flow events
- feat(scripts): add country and region columns to activity/flow events
Conclusion
Looking back, I think it was a productive year, all things considered:
I learned Rust.
I took a new microservice for the FxA stack all the way from planning to deployment in production.
I made proactive and meaningful contributions with tangible outcomes, in response to operational issues that we hit in production.
I kept the FxA metrics infrastructure up and running, fixing issues as they showed up in the data and deploying code for new metrics as the needs arose.
I fixed a whole bunch of different issues across a whole bunch of different repositories, using a whole bunch of different languages.
Looking ahead and keeping myself honest for 2019, I’d like to continue working with Rust and go back to using a LISP again for some of my side-projects.
Away from programming, I want to pass my driving test, learn French and pass my WSET level 3.